Come impostare l'inserimento delle dipendenze DAGGER da zero nel progetto Android?


100

Come usare Dagger? Come configurare Dagger per funzionare nel mio progetto Android?

Mi piacerebbe usare Dagger nel mio progetto Android, ma lo trovo confuso.

EDIT: Dagger2 è anche uscito dal 2015 04 15, ed è ancora più confuso!

[Questa domanda è uno "stub" su cui aggiungo alla mia risposta mentre apprendo di più su Dagger1 e apprendo di più su Dagger2. Questa domanda è più una guida che una "domanda".]



Grazie per aver condiviso questo. Hai conoscenze su come inserire classi ViewModel? La mia classe ViewModel è senza @AssistedInject ma ha dipendenze che possono essere fornite dal grafico Dagger?
AndroidDev


Un'altra domanda, con Dagger2, è possibile avere un oggetto e il suo riferimento è condiviso da ViewModele PageKeyedDataSource? Come uso RxJava2 e voglio che CompositeDisposable sia condiviso da entrambe le classi e se l'utente preme il pulsante Indietro, desidero cancellare l'oggetto Disposable. Ho così scarse caso aggiunto qui: stackoverflow.com/questions/62595956/...
AndroidDev

ViewModelFaresti meglio a mettere il compositeDisposable all'interno e forse passare lo stesso compositeDisposable come argomento del costruttore del tuo PageKeyedDataSource personalizzato, ma non userei davvero Dagger per quella parte perché allora hai bisogno di sottocomponenti con sottoscrizione e Hilt non lo supporterà davvero facile per te.
EpicPandaForce

Risposte:


193

Guida per Dagger 2.x (edizione rivista 6) :

I passaggi sono i seguenti:

1.) aggiungi Daggerai tuoi build.gradlefile:

  • build.gradle di primo livello :

.

// 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()
    }
}
  • livello di app build.gradle :

.

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.) Crea la tua AppContextModuleclasse che fornisce le dipendenze.

@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.) creare la AppContextComponentclasse che fornisce l'interfaccia per ottenere le classi iniettabili.

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

3.1.) Ecco come creeresti un modulo con un'implementazione:

@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);
}

Attenzione: è necessario fornire l' @Scopeannotazione (come @Singletono @ActivityScope) sul @Providesmetodo annotato del modulo per ottenere un provider con ambito all'interno del componente generato, altrimenti sarà privo di ambito e riceverai una nuova istanza ogni volta che inietti.

3.2.) Creare un componente con ambito applicazione che specifichi cosa è possibile iniettare (questo è lo stesso injects={MainActivity.class}di 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.) Per le dipendenze che puoi creare tu stesso tramite un costruttore e non vorrai ridefinirle usando a @Module(ad esempio, usi i gusti di compilazione invece di cambiare il tipo di implementazione), puoi usare il @Injectcostruttore annotato.

public class Something {
    OtherThing otherThing;

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

Inoltre, se usi il @Injectcostruttore, puoi usare l'iniezione di campi senza dover chiamare esplicitamente component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Queste @Injectclassi del costruttore vengono aggiunte automaticamente al componente dello stesso ambito senza doverle specificare esplicitamente in un modulo.

Una classe di costruttore con @Singletonambito @Injectverrà visualizzata nei @Singletoncomponenti con ambito.

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

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

3.4.) Dopo aver definito un'implementazione specifica per una data interfaccia, in questo modo:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Dovrai "associare" l'implementazione specifica all'interfaccia con un file @Module.

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

Un'abbreviazione per questo dato che Dagger 2.4 è la seguente:

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

4.) crea una Injectorclasse per gestire il tuo componente a livello di applicazione (sostituisce il monolitico ObjectGraph)

(nota: Rebuild Projectper creare la DaggerApplicationComponentclasse builder utilizzando 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.) crea la tua CustomApplicationclasse

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

6.) aggiungi CustomApplicational tuo AndroidManifest.xml.

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

7.) Inietta le tue classiMainActivity

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.) Buon divertimento!

+1.) È possibile specificare Scopeper i componenti con cui creare componenti con ambito a livello di attività . Gli ambiti secondari consentono di fornire le dipendenze necessarie solo per un determinato ambito secondario, piuttosto che per l'intera applicazione. In genere, ogni attività riceve il proprio modulo con questa configurazione. Si noti che esiste un provider con ambito per componente , il che significa che per mantenere l'istanza per tale attività, il componente stesso deve sopravvivere alla modifica della configurazione. Ad esempio, potrebbe sopravvivere attraverso onRetainCustomNonConfigurationInstance(), o un mirino di mortaio.

Per maggiori informazioni sull'abbonamento, consulta la guida di Google . Si prega inoltre di consultare questo sito sui metodi di fornitura e anche la sezione delle dipendenze dei componenti ) e qui .

Per creare un ambito personalizzato, è necessario specificare l'annotazione del qualificatore dell'ambito:

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

Per creare un ambito secondario, è necessario specificare l'ambito sul componente e specificarlo ApplicationComponentcome sua dipendenza. Ovviamente è necessario specificare anche l'ambito secondario sui metodi del provider del modulo.

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

    void inject(YourScopedClass scopedClass);
}

E

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

Si noti che solo un componente con ambito può essere specificato come dipendenza. Pensa esattamente allo stesso modo in cui l'ereditarietà multipla non è supportata in Java.

+2.) Informazioni su @Subcomponent: essenzialmente, uno scoped @Subcomponentpuò sostituire una dipendenza del componente; ma invece di usare un builder fornito dal processore di annotazioni, dovresti usare un metodo di fabbrica dei componenti.

Così questo:

@Singleton
@Component
public interface ApplicationComponent {
}

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

    void inject(YourScopedClass scopedClass);
}

Diventa questo:

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

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

E questo:

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

Diventa questo:

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

+3.): Controlla anche altre domande su Stack Overflow riguardanti Dagger2, che forniscono molte informazioni. Ad esempio, la mia attuale struttura Dagger2 è specificata in questa risposta .

Grazie

Grazie per le guide su Github , TutsPlus , Joe Steele , Froger MCS e Google .

Anche per questa guida passo passo alla migrazione che ho trovato dopo aver scritto questo post.

E per la spiegazione dell'ambito di Kirill.

Ancora più informazioni nella documentazione ufficiale .


Credo che ci manchi l'implementazione di DaggerApplicationComponent
Thanasis Kapelonis

1
@ThanasisKapelonis DaggerApplicationComponentè autogenerato da APT nella build, ma lo aggiungerò.
EpicPandaForce

1
Dovevo solo rendere pubblico il metodo Injector.initializeApplicationComponent poiché la mia CustomApplication era al di fuori dell'ambito del pacchetto e tutto funziona perfettamente! Grazie!
Juan Saravia

2
Un po 'tardi, ma forse i seguenti esempi aiuteranno chiunque: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski

1
Se viene visualizzato "Avviso: utilizzo di plug-in incompatibili per l'elaborazione delle annotazioni: android-apt. Ciò potrebbe provocare un comportamento imprevisto. " Nel passaggio 1, modifica apt "com.google.dagger: dagger-compiler: 2.7" in annotationProcessor "com.google.dagger: dagger-compiler: 2.7" e rimuovi tutta la configurazione di apt. I dettagli possono essere trovati qui bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84

11

Guida per Dagger 1.x :

I passaggi sono i seguenti:

1.) aggiungi Daggeral build.gradlefile per le dipendenze

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

Inoltre, aggiungi packaging-optionper evitare un errore su duplicate APKs.

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

2.) creare una Injectorclasse per gestire il file 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.) Crea un RootModuleper collegare insieme i tuoi moduli futuri. Nota che devi includere injectsper specificare ogni classe in cui utilizzerai l' @Injectannotazione, perché altrimenti Dagger lancia RuntimeException.

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

4.) Nel caso in cui tu abbia altri sotto-moduli all'interno dei tuoi moduli specificati nella tua radice, crea moduli per quelli:

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

5.) creare i moduli foglia che ricevono le dipendenze come parametri del costruttore. Nel mio caso, non c'era dipendenza circolare, quindi non so se Dagger può risolverlo, ma lo trovo improbabile. I parametri del costruttore devono essere forniti anche in un modulo di Dagger, se lo specifichi complete = falsepuò essere anche in altri moduli.

@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.) Estendi Applicatione inizializza il file Injector.

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

7.) Nel tuo MainActivity, chiama l'iniettore nel onCreate()metodo.

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

8.) Usa @Injectnel tuo MainActivity.

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

...

Se ricevi l'errore no injectable constructor found, assicurati di non aver dimenticato le @Providesannotazioni.


Questa risposta è in parte basata sul codice generato da Android Bootstrap. Quindi, grazie a loro per averlo capito. La soluzione utilizza Dagger v1.2.2.
EpicPandaForce

3
Lo scopo di dagger-compilerdovrebbe essere providedaltrimenti verrà incluso nell'applicazione ed è sotto licenza GPL.
Denis Kniazhev

@deniskniazhev oh, non lo sapevo! Grazie per il testa a testa!
EpicPandaForce
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.