Comment configurer l'injection de dépendance DAGGER à partir de zéro dans un projet Android?

100

Comment utiliser Dagger? Comment configurer Dagger pour qu'il fonctionne dans mon projet Android?

J'aimerais utiliser Dagger dans mon projet Android, mais je trouve cela déroutant.

EDIT: Dagger2 est également sorti depuis 2015 04 15, et c'est encore plus déroutant!

[Cette question est un «talon» sur lequel j'ajoute à ma réponse au fur et à mesure que j'en apprends plus sur Dagger1 et que j'en apprends plus sur Dagger2. Cette question est plus un guide qu'une "question".]

EpicPandaForce
la source
Voir aussi: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Merci d'avoir partagé ça. Avez-vous des connaissances sur la façon d'injecter des classes ViewModel? Ma classe ViewModel est sans @AssistedInject mais elle a des dépendances qui peuvent être fournies par Dagger graph?
AndroidDev
1
Bien sûr, voir stackoverflow.com/questions/60884402/…
EpicPandaForce
Encore une question, avec Dagger2, est-il possible d'avoir un objet et sa référence est partagée par ViewModelet PageKeyedDataSource? Comme j'utilise RxJava2, et je veux que CompositeDisposable soit partagé par les deux classes et si l'utilisateur appuie sur le bouton retour, je veux effacer l'objet jetable. J'ai ajouté un cas ici: stackoverflow.com/questions/62595956/…
AndroidDev
Vous feriez mieux de mettre le compositeDisposable à l'intérieur ViewModelet peut-être de passer le même compositeDisposable en tant qu'argument constructeur de votre PageKeyedDataSource personnalisée, mais je n'utiliserais pas vraiment Dagger pour cette partie parce que vous avez alors besoin de sous-composants sous-cadrés, et Hilt ne le supportera pas vraiment. facile pour toi.
EpicPandaForce le

Réponses:

193

Guide pour Dagger 2.x (édition révisée 6) :

Les étapes sont les suivantes:

1.) ajoutez Daggerà vos build.gradlefichiers:

  • build.gradle de niveau supérieur :

.

// 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 au niveau de l' application :

.

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.) Créez votre AppContextModuleclasse qui fournit les dépendances.

@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.) créez la AppContextComponentclasse qui fournit l'interface pour obtenir les classes injectables.

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

3.1.) Voici comment créer un module avec une implémentation:

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

Attention: vous devez fournir l' @Scopeannotation (comme @Singletonou @ActivityScope) sur la @Providesméthode annotée du module pour obtenir un fournisseur de portée dans votre composant généré, sinon il sera sans portée, et vous obtiendrez une nouvelle instance à chaque fois que vous injecterez.

3.2.) Créez un composant d'application qui spécifie ce que vous pouvez injecter (c'est le même que celui injects={MainActivity.class}de 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.) Pour les dépendances que vous pouvez créer vous-même via un constructeur et que vous ne voudrez pas redéfinir en utilisant a @Module(par exemple, vous utilisez plutôt des versions de build pour changer le type d'implémentation), vous pouvez utiliser un @Injectconstructeur annoté.

public class Something {
    OtherThing otherThing;

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

De plus, si vous utilisez un @Injectconstructeur, vous pouvez utiliser l'injection de champ sans avoir à appeler explicitement component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Ces @Injectclasses de constructeur sont automatiquement ajoutées au composant de même portée sans avoir à les spécifier explicitement dans un module.

Une classe de constructeur à @Singletonportée @Injectsera vue dans les @Singletoncomposants à portée.

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

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

3.4.) Après avoir défini une implémentation spécifique pour une interface donnée, comme ceci:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Vous devrez "lier" l'implémentation spécifique à l'interface avec un fichier @Module.

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

Un raccourci pour cela depuis Dagger 2.4 est le suivant:

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

4.) créez une Injectorclasse pour gérer votre composant au niveau de l'application (il remplace le monolithique ObjectGraph)

(note: Rebuild Projectpour créer la DaggerApplicationComponentclasse de générateur en utilisant 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.) créez votre CustomApplicationclasse

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

6.) ajoutez CustomApplicationà votre AndroidManifest.xml.

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

7.) Injectez vos classes dansMainActivity

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.) Profitez-en!

+1.) Vous pouvez spécifier Scopepour vos composants avec lesquels vous pouvez créer des composants étendus au niveau de l'activité . Les sous-étendues vous permettent de fournir des dépendances dont vous n'avez besoin que pour une sous-étendue donnée, plutôt que dans toute l'application. En règle générale, chaque activité possède son propre module avec cette configuration. Veuillez noter qu'un fournisseur de portée existe par composant , ce qui signifie que pour conserver l'instance pour cette activité, le composant lui-même doit survivre au changement de configuration. Par exemple, il pourrait survivre à travers onRetainCustomNonConfigurationInstance(), ou une lunette de mortier.

Pour plus d'informations sur le sous-champ, consultez le guide de Google . Veuillez également consulter ce site sur les méthodes de mise à disposition ainsi que la section sur les dépendances des composants ) et ici .

Pour créer une étendue personnalisée, vous devez spécifier l'annotation du qualificatif d'étendue:

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

Pour créer une sous-étendue, vous devez spécifier la portée de votre composant et la spécifier ApplicationComponentcomme sa dépendance. De toute évidence, vous devez également spécifier la sous-étendue sur les méthodes du fournisseur de module.

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

    void inject(YourScopedClass scopedClass);
}

Et

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

S'il vous plaît noter que seule une composante scope peut être spécifié comme une dépendance. Pensez-y exactement comme l'héritage multiple n'est pas pris en charge en Java.

+2.) A propos @Subcomponent: essentiellement, une portée @Subcomponentpeut remplacer une dépendance de composant; mais plutôt que d'utiliser un générateur fourni par le processeur d'annotations, vous devrez utiliser une méthode de fabrique de composants.

Donc ça:

@Singleton
@Component
public interface ApplicationComponent {
}

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

    void inject(YourScopedClass scopedClass);
}

Devient ceci:

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

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

Et ça:

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

Devient ceci:

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

+3.): Veuillez vérifier également les autres questions de Stack Overflow concernant Dagger2, elles fournissent beaucoup d'informations. Par exemple, ma structure Dagger2 actuelle est spécifiée dans cette réponse .

Merci

Merci pour les guides de Github , TutsPlus , Joe Steele , Froger MCS et Google .

Aussi pour ce guide de migration étape par étape, j'ai trouvé après avoir écrit ce post.

Et pour l' explication de la portée par Kirill.

Encore plus d'informations dans la documentation officielle .

EpicPandaForce
la source
Je crois qu'il nous manque l'implémentation de DaggerApplicationComponent
Thanasis Kapelonis
1
@ThanasisKapelonis DaggerApplicationComponentest généré automatiquement par APT lors de la construction, mais je vais l'ajouter.
EpicPandaForce
1
Je devais juste rendre publique la méthode Injector.initializeApplicationComponent car mon CustomApplication était en dehors de la portée du package et tout fonctionne parfaitement! Merci!
Juan Saravia
2
Un peu tard mais peut-être que les exemples suivants aideront n'importe qui: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski
1
Si vous obtenez 'Avertissement: Utilisation de plugins incompatibles pour le traitement des annotations: android-apt. Cela peut entraîner un comportement inattendu. ' À l'étape 1, remplacez apt 'com.google.dagger: dagger-compiler: 2.7' par annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' et supprimez toute la configuration d'apt. Les détails peuvent être trouvés ici bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84
11

Guide pour Dagger 1.x :

Les étapes sont les suivantes:

1.) ajouter Daggerau build.gradlefichier pour les dépendances

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

Ajoutez également packaging-optionpour éviter une erreur sur duplicate APKs.

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

2.) créez une Injectorclasse pour gérer le fichier 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.) Créez un RootModulepour relier vos futurs modules entre eux. Veuillez noter que vous devez inclure injectspour spécifier chaque classe dans laquelle vous utiliserez l' @Injectannotation, car sinon Dagger lance RuntimeException.

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

4.) Si vous avez d'autres sous-modules dans vos modules spécifiés dans votre racine, créez des modules pour ceux-ci:

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

5.) créez les modules feuilles qui reçoivent les dépendances en tant que paramètres de constructeur. Dans mon cas, il n'y avait pas de dépendance circulaire, donc je ne sais pas si Dagger peut résoudre cela, mais je trouve cela peu probable. Les paramètres du constructeur doivent également être fournis dans un module par Dagger, si vous spécifiez, complete = falsecela peut également être dans d'autres modules.

@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.) Étendez Applicationet initialisez le fichier Injector.

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

7.) Dans votre MainActivity, appelez l'injecteur dans la onCreate()méthode.

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

8.) Utilisez @Injectdans votre MainActivity.

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

...

Si vous obtenez l'erreur no injectable constructor found, assurez-vous de ne pas oublier les @Providesannotations.

EpicPandaForce
la source
Cette réponse est en partie basée sur le code généré par Android Bootstrap. Alors, merci à eux de l'avoir compris. La solution utilise Dagger v1.2.2.
EpicPandaForce
3
La portée de dagger-compilerdevrait être providedsinon elle sera incluse dans l'application, et c'est sous licence GPL.
Denis Kniazhev
@deniskniazhev oh, je ne le savais pas! Merci pour l'information!
EpicPandaForce