Jak skonfigurować wstrzykiwanie zależności DAGGER od podstaw w projekcie na Androida?

100

Jak używać sztyletu? Jak skonfigurować Dagger do pracy w moim projekcie na Androida?

Chciałbym użyć Daggera w moim projekcie na Androida, ale wydaje mi się to zagmatwane.

EDYCJA: Dagger2 jest również dostępny od 15.04.2015 r. I jest jeszcze bardziej zagmatwany!

[To pytanie jest „odgałęzieniem”, na którym dodaję do mojej odpowiedzi, gdy dowiaduję się więcej o Dagger1 i dowiaduję się więcej o Dagger2. To pytanie jest raczej przewodnikiem niż „pytaniem”.]

EpicPandaForce
źródło
Zobacz też: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Dzięki za udostępnienie tego. Czy masz wiedzę na temat wstrzykiwania klas ViewModel? Moja klasa ViewModel nie ma @AssistedInject, ale ma zależności, które mogą być dostarczone przez wykres Daggera?
AndroidDev
1
Jasne, zobacz stackoverflow.com/questions/60884402/ ...
EpicPandaForce
Jeszcze jedno pytanie, w przypadku Daggera2, czy można mieć obiekt i jego odniesienie jest wspólne dla ViewModeli PageKeyedDataSource? Tak jak używam RxJava2 i chcę, aby CompositeDisposable było współdzielone przez obie klasy, a jeśli użytkownik naciśnie przycisk Wstecz, chcę wyczyścić obiekt Disposable. Dodałem tutaj przypadek: stackoverflow.com/questions/62595956/ ...
AndroidDev
Lepiej jest umieścić CompositeDisposable w środku ViewModeli być może przekazać ten sam argument compositeDisposable jako konstruktora Twojego niestandardowego PageKeyedDataSource, ale tak naprawdę nie użyłbym Daggera do tej części, ponieważ wtedy potrzebujesz podkomponentów podrzędnych, a Hilt tak naprawdę nie będzie wspierać tego Łatwe dla Ciebie.
EpicPandaForce

Odpowiedzi:

193

Przewodnik po Dagger 2.x (poprawiona edycja 6) :

Kroki są następujące:

1.) dodaj Daggerdo swoich build.gradleplików:

  • build.gradle najwyższego poziomu :

.

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //added apt for source code generation
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
  • build.gradle na poziomie aplikacji :

.

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' //needed for source code generation

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"

    defaultConfig {
        applicationId "your.app.id"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    apt 'com.google.dagger:dagger-compiler:2.7' //needed for source code generation
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.google.dagger:dagger:2.7' //dagger itself
    provided 'org.glassfish:javax.annotation:10.0-b28' //needed to resolve compilation errors, thanks to tutplus.org for finding the dependency
}

2.) Utwórz AppContextModuleklasę, która zawiera zależności.

@Module //a module could also include other modules
public class AppContextModule {
    private final CustomApplication application;

    public AppContextModule(CustomApplication application) {
        this.application = application;
    }

    @Provides
    public CustomApplication application() {
        return this.application;
    }

    @Provides 
    public Context applicationContext() {
        return this.application;
    }

    @Provides
    public LocationManager locationService(Context context) {
        return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }
}

3.) Utwórz AppContextComponentklasę udostępniającą interfejs do pobierania klas, które można wstrzyknąć.

public interface AppContextComponent {
    CustomApplication application(); //provision method
    Context applicationContext(); //provision method
    LocationManager locationManager(); //provision method
}

3.1.) Oto jak stworzyłbyś moduł z implementacją:

@Module //this is to show that you can include modules to one another
public class AnotherModule {
    @Provides
    @Singleton
    public AnotherClass anotherClass() {
        return new AnotherClassImpl();
    }
}

@Module(includes=AnotherModule.class) //this is to show that you can include modules to one another
public class OtherModule {
    @Provides
    @Singleton
    public OtherClass otherClass(AnotherClass anotherClass) {
        return new OtherClassImpl(anotherClass);
    }
}

public interface AnotherComponent {
    AnotherClass anotherClass();
}

public interface OtherComponent extends AnotherComponent {
    OtherClass otherClass();
}

@Component(modules={OtherModule.class})
@Singleton
public interface ApplicationComponent extends OtherComponent {
    void inject(MainActivity mainActivity);
}

Uwaga: Musisz podać @Scopeadnotację (taką jak @Singletonlub @ActivityScope) w @Providesmetodzie z adnotacjami modułu, aby uzyskać zakres dostawcy w wygenerowanym komponencie, w przeciwnym razie będzie on bez zakresu i za każdym razem otrzymasz nową instancję.

3.2.) Utwórz komponent o zasięgu aplikacji, który określa, co możesz wstrzyknąć (jest to to samo, co injects={MainActivity.class}w Dagger 1.x):

@Singleton
@Component(module={AppContextModule.class}) //this is where you would add additional modules, and a dependency if you want to subscope
public interface ApplicationComponent extends AppContextComponent { //extend to have the provision methods
    void inject(MainActivity mainActivity);
}

3.3.) W przypadku zależności, które możesz samodzielnie utworzyć za pomocą konstruktora i nie chcesz ich przedefiniować za pomocą @Module(na przykład, zamiast tego używasz smaków kompilacji, aby zmienić typ implementacji), możesz użyć @Injectkonstruktora z adnotacjami.

public class Something {
    OtherThing otherThing;

    @Inject
    public Something(OtherThing otherThing) {
        this.otherThing = otherThing;
    }
}

Ponadto, jeśli używasz @Injectkonstruktora, możesz użyć iniekcji pola bez konieczności jawnego wywoływania component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Te @Injectklasy konstruktorów są automatycznie dodawane do składnika o tym samym zakresie bez konieczności jawnego określania ich w module.

@SingletonScoped @Injectklasy konstruktor będzie można zobaczyć w @Singletonscoped komponentów.

@Singleton // scoping
public class Something {
    OtherThing otherThing;

    @Inject
    public Something(OtherThing otherThing) {
        this.otherThing = otherThing;
    }
}

3.4.) Po zdefiniowaniu konkretnej implementacji dla danego interfejsu, na przykład:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Będziesz musiał „powiązać” konkretną implementację z interfejsem za pomocą pliku @Module.

@Module
public class SomethingModule {
    @Provides
    Something something(SomethingImpl something) {
        return something;
    }
}

Krótka ręka na to od Daggera 2.4 jest następująca:

@Module
public abstract class SomethingModule {
    @Binds
    abstract Something something(SomethingImpl something);
}

4.) utwórz Injectorklasę do obsługi komponentu na poziomie aplikacji (zastępuje monolit ObjectGraph)

(uwaga: Rebuild Projectaby utworzyć DaggerApplicationComponentklasę konstruktora za pomocą APT)

public enum Injector {
    INSTANCE;

    ApplicationComponent applicationComponent;

    private Injector(){
    }

    static void initialize(CustomApplication customApplication) {
        ApplicationComponent applicationComponent = DaggerApplicationComponent.builder()
           .appContextModule(new AppContextModule(customApplication))
           .build();
        INSTANCE.applicationComponent = applicationComponent;
    }

    public static ApplicationComponent get() {
        return INSTANCE.applicationComponent;
    }
}

5.) stwórz swoją CustomApplicationklasę

public class CustomApplication
        extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Injector.initialize(this);
    }
}

6.) dodaj CustomApplicationdo swojego AndroidManifest.xml.

<application
    android:name=".CustomApplication"
    ...

7.) Wstrzyknij swoje zajęcia w formacieMainActivity

public class MainActivity
        extends AppCompatActivity {
    @Inject
    CustomApplication customApplication;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Injector.get().inject(this);
        //customApplication is injected from component
    }
}

8.) Ciesz się!

+1.) Możesz określić Scopedla swoich komponentów, za pomocą których możesz tworzyć komponenty o zakresie na poziomie działania . Podzakresy pozwalają na zapewnienie zależności, których potrzebujesz tylko dla danego zakresu, a nie w całej aplikacji. Zazwyczaj każde działanie otrzymuje swój własny moduł w tej konfiguracji. Należy pamiętać, że dla każdego komponentu istnieje dostawca w zakresie , co oznacza, że ​​aby zachować instancję dla tego działania, sam komponent musi przetrwać zmianę konfiguracji. Na przykład może przetrwać onRetainCustomNonConfigurationInstance()lub przez lunetę z moździerza.

Aby uzyskać więcej informacji na temat subscopingu, zapoznaj się z przewodnikiem Google . Proszę również odwiedzić tę witrynę o metodach udostępniania, a także sekcję dotyczącą zależności komponentów ) oraz tutaj .

Aby utworzyć zakres niestandardowy, musisz określić adnotację kwalifikatora zakresu:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface YourCustomScope {
}

Aby utworzyć zakres podrzędny, musisz określić zakres w swoim komponencie i określić ApplicationComponentjako jego zależność. Oczywiście musisz również określić zakres podrzędny w metodach dostawcy modułów.

@YourCustomScope
@Component(dependencies = {ApplicationComponent.class}, modules = {CustomScopeModule.class})
public interface YourCustomScopedComponent
        extends ApplicationComponent {
    CustomScopeClass customScopeClass();

    void inject(YourScopedClass scopedClass);
}

I

@Module
public class CustomScopeModule {
    @Provides
    @YourCustomScope
    public CustomScopeClass customScopeClass() {
        return new CustomScopeClassImpl();
    }
}

Należy pamiętać, że jako zależność można określić tylko jeden składnik o określonym zakresie. Pomyśl o tym dokładnie tak, jak o tym, że dziedziczenie wielokrotne nie jest obsługiwane w Javie.

+2.) O @Subcomponent: zasadniczo zakres @Subcomponentmoże zastąpić zależność komponentu; ale zamiast korzystać z konstruktora dostarczonego przez procesor adnotacji, należałoby użyć metody fabryki komponentów.

Więc to:

@Singleton
@Component
public interface ApplicationComponent {
}

@YourCustomScope
@Component(dependencies = {ApplicationComponent.class}, modules = {CustomScopeModule.class})
public interface YourCustomScopedComponent
        extends ApplicationComponent {
    CustomScopeClass customScopeClass();

    void inject(YourScopedClass scopedClass);
}

Staje się tym:

@Singleton
@Component
public interface ApplicationComponent {
    YourCustomScopedComponent newYourCustomScopedComponent(CustomScopeModule customScopeModule);
}

@Subcomponent(modules={CustomScopeModule.class})
@YourCustomScope
public interface YourCustomScopedComponent {
    CustomScopeClass customScopeClass();
}

I to:

DaggerYourCustomScopedComponent.builder()
      .applicationComponent(Injector.get())
      .customScopeModule(new CustomScopeModule())
      .build();

Staje się tym:

Injector.INSTANCE.newYourCustomScopedComponent(new CustomScopeModule());

+3.): Sprawdź również inne pytania dotyczące przepełnienia stosu dotyczące Daggera2, zawierają one wiele informacji. Na przykład moja obecna struktura Dagger2 jest określona w tej odpowiedzi .

Dzięki

Dziękuję za przewodniki w Github , TutsPlus , Joe Steele , Froger MCS i Google .

Również w przypadku tego przewodnika migracji krok po kroku, który znalazłem po napisaniu tego posta.

I dla wyjaśnienia zakresu przez Kirilla.

Jeszcze więcej informacji w oficjalnej dokumentacji .

EpicPandaForce
źródło
Myślę, że brakuje nam implementacji DaggerApplicationComponent
Thanasis Kapelonis
1
@ThanasisKapelonis DaggerApplicationComponentjest automatycznie generowany przez APT podczas kompilacji, ale dodam go.
EpicPandaForce
1
Musiałem tylko upublicznić metodę Injector.initializeApplicationComponent, ponieważ moja CustomApplication znajdowała się poza zakresem pakietu i wszystko działa idealnie! Dzięki!
Juan Saravia
2
Trochę późno, ale może poniższe przykłady pomogą każdemu: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid
1
Jeśli pojawi się komunikat „Ostrzeżenie: używanie niekompatybilnych wtyczek do przetwarzania adnotacji: android-apt. Może to spowodować nieoczekiwane zachowanie ”. W kroku 1 zmień apt „com.google.dagger: dagger-compiler: 2.7” na annotationProcessor „com.google.dagger: dagger-compiler: 2.7” i usuń całą konfigurację apt. Szczegóły można znaleźć tutaj bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84
11

Poradnik do Dagger 1.x :

Kroki są następujące:

1.) dodaj Daggerdo build.gradlepliku zależności

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    ...
    compile 'com.squareup.dagger:dagger:1.2.2'
    provided 'com.squareup.dagger:dagger-compiler:1.2.2'

Dodaj także, packaging-optionaby uniknąć błędu dotyczącego duplicate APKs.

android {
    ...
    packagingOptions {
        // Exclude file to avoid
        // Error: Duplicate files during packaging of APK
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }
}

2.) Utwórz Injectorklasę do obsługi ObjectGraph.

public enum Injector
{
    INSTANCE;

    private ObjectGraph objectGraph = null;

    public void init(final Object rootModule)
    {

        if(objectGraph == null)
        {
            objectGraph = ObjectGraph.create(rootModule);
        }
        else
        {
            objectGraph = objectGraph.plus(rootModule);
        }

        // Inject statics
        objectGraph.injectStatics();

    }

    public void init(final Object rootModule, final Object target)
    {
        init(rootModule);
        inject(target);
    }

    public void inject(final Object target)
    {
        objectGraph.inject(target);
    }

    public <T> T resolve(Class<T> type)
    {
        return objectGraph.get(type);
    }
}

3.) Utwórz, RootModuleaby połączyć ze sobą przyszłe moduły. Pamiętaj, że musisz injectspodać każdą klasę, w której będziesz używać @Injectadnotacji, ponieważ w przeciwnym razie rzuca Dagger RuntimeException.

@Module(
    includes = {
        UtilsModule.class,
        NetworkingModule.class
    },
    injects = {
        MainActivity.class
    }
)
public class RootModule
{
}

4.) W przypadku, gdy masz inne podmoduły w swoich modułach określonych w katalogu głównym, utwórz moduły dla tych:

@Module(
    includes = {
        SerializerModule.class,
        CertUtilModule.class
    }
)
public class UtilsModule
{
}

5.) stworzyć moduły-liście, które otrzymają zależności jako parametry konstruktora. W moim przypadku nie było zależności cyklicznej, więc nie wiem, czy Dagger może to rozwiązać, ale wydaje mi się to mało prawdopodobne. Parametry konstruktora muszą być również podane w Module przez Dagger, jeśli określisz complete = false, mogą być również w innych modułach.

@Module(complete = false, library = true)
public class NetworkingModule
{
    @Provides
    public ClientAuthAuthenticator providesClientAuthAuthenticator()
    {
        return new ClientAuthAuthenticator();
    }

    @Provides
    public ClientCertWebRequestor providesClientCertWebRequestor(ClientAuthAuthenticator clientAuthAuthenticator)
    {
        return new ClientCertWebRequestor(clientAuthAuthenticator);
    }

    @Provides
    public ServerCommunicator providesServerCommunicator(ClientCertWebRequestor clientCertWebRequestor)
    {
        return new ServerCommunicator(clientCertWebRequestor);
    }
}

6.) Rozszerz Applicationi zainicjuj plik Injector.

@Override
public void onCreate()
{
    super.onCreate();
    Injector.INSTANCE.init(new RootModule());
}

7.) W swoim MainActivity, wywołaj Injector w onCreate()metodzie.

@Override
protected void onCreate(Bundle savedInstanceState)
{
    Injector.INSTANCE.inject(this);
    super.onCreate(savedInstanceState);
    ...

8.) Użyj @Injectw swoim MainActivity.

public class MainActivity extends ActionBarActivity
{  
    @Inject
    public ServerCommunicator serverCommunicator;

...

Jeśli pojawi się błąd no injectable constructor found, upewnij się, że nie zapomniałeś @Providesadnotacji.

EpicPandaForce
źródło
Ta odpowiedź jest częściowo oparta na kodzie wygenerowanym przez Android Bootstrap. Więc chwała im za to, że to zrozumieli. Rozwiązanie wykorzystuje Dagger v1.2.2.
EpicPandaForce
3
Zakres dagger-compilerpowinien być providedinaczej zostanie on włączony do aplikacji, a to na podstawie licencji GPL.
Denis Kniazhev
@deniskniazhev och, nie wiedziałem tego! Dzięki za ostrzeżenie!
EpicPandaForce