Dagger- Devrions-nous créer chaque composant et module pour chaque activité / fragment?

Je travaille avec dagger2 depuis un moment. Et je me suis trompé sur la création d’un composant / module propre à chaque activité / fragment. S’il vous plaît aidez-moi à clarifier ceci:

Par exemple, nous avons une application et l’application dispose d’environ 50 écrans. Nous allons implémenter le code suivant le modèle MVP et Dagger2 pour DI. Supposons que nous ayons 50 activités et 50 présentateurs.

À mon avis, nous devrions généralement organiser le code comme ceci:

  1. Créez un AppComponent et un AppModule qui fourniront tous les objects qui seront utilisés lorsque l’application est ouverte.

    @Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other providers } @Singleton @Component( modules = { AppModule.class } ) public interface AppComponent { Context getAppContext(); Activity1Component plus(Activity1Module module); Activity2Component plus(Activity2Module module); //... plus 48 methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....) } 
  2. Créer ActivityScope:

     @Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { } 
  3. Créez un composant et un module pour chaque activité. D’habitude je les mettrai en classes statiques dans la classe Activity:

     @Module public class Activity1Module { public LoginModule() { } @Provides @ActivityScope Activity1Presenter provideActivity1Presenter(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } } @ActivityScope @Subcomponent( modules = { Activity1Module.class } ) public interface Activity1Component { void inject(Activity1 activity); // inject Presenter to the Activity } // .... Same with 49 remaining modules and components. 

Ce ne sont que des exemples très simples pour montrer comment je mettrais cela en œuvre.

Mais un de mes amis vient de me donner une autre implémentation:

  1. Créez PresenterModule qui fournira à tous les présentateurs:

     @Module public class AppPresenterModule { @Provides Activity1Presenter provideActivity1Presentor(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } @Provides Activity2Presenter provideActivity2Presentor(Context context, /*...some other params*/){ return new Activity2PresenterImpl(context, /*...some other params*/); } //... same with 48 other presenters. } 
  2. Créez AppModule et AppComponent:

     @Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other provides } @Singleton @Component( modules = { AppModule.class, AppPresenterModule.class } ) public interface AppComponent { Context getAppContext(); public void inject(Activity1 activity); public void inject(Activity2 activity); //... and 48 other methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....) } 

Son explication est la suivante: il n’a pas besoin de créer des composants et des modules pour chaque activité. Je pense que l’idée de mes amis n’est absolument pas bonne, mais corrigez-moi si je me trompe. Voici les raisons:

  1. Beaucoup de memory leaks :

    • L’application créera 50 présentateurs même si l’utilisateur n’a que 2 activités ouvertes.
    • Après la fermeture d’une activité par l’utilisateur, son présentateur restra toujours
  2. Que se passe-t-il si je veux créer deux instances d’une activité? (comment peut-il créer deux présentateurs)

  3. L’initialisation de l’application prendra beaucoup de temps (car il doit créer de nombreux présentateurs, objects, …)

Désolé pour un long message, mais aidez-moi s’il vous plaît à clarifier ceci pour moi et mon ami, je ne peux pas le convaincre. Vos commentaires seront très appréciés.

/ ————————————————- ———————- /

Modifier après avoir fait une démo.

Tout d’abord, merci pour la réponse de @pandawarrior. J’aurais dû créer une démo avant de poser cette question. J’espère que ma conclusion ici pourrait aider quelqu’un d’autre.

  1. Ce que mon ami a fait ne provoque pas de memory leaks, à moins qu’il ne mette une Scope dans les méthodes Provides. (Par exemple, @Singleton ou @UserScope, …)
  2. Nous pouvons créer de nombreux présentateurs, si la méthode Provides n’a aucune scope. (Donc, mon deuxième point est faux aussi)
  3. Dagger créera les présentateurs uniquement quand ils en auront besoin. (Donc, l’application ne prendra pas beaucoup de temps à initialiser, j’ai été confondu par Lazy Injection)

Donc, toutes les raisons que j’ai dites ci-dessus sont pour la plupart fausses. Mais cela ne signifie pas que nous devrions suivre l’idée de mon ami, pour deux raisons:

  1. Ce n’est pas bon pour l’architecture de la source, quand il place tous les présentateurs dans le module / composant. (Cela viole le principe de séparation des interfaces , peut-être aussi le principe de la responsabilité unique ).

  2. Lorsque nous créons un composant Scope, nous saurons quand il est créé et quand il est détruit, ce qui représente un avantage énorme pour éviter les memory leaks. Donc, pour chaque activité, nous devrions créer un composant avec un @ActivityScope. Imaginons, avec l’implémentation de mes amis, que nous avons oublié de placer Scope dans la méthode Provider => des memory leaks se produiront.

À mon avis, avec une petite application (quelques écrans dépourvus de nombreuses dépendances ou avec des dépendances similaires), nous pourrions appliquer l’idée de mes amis, mais bien sûr, ce n’est pas recommandé.

Vous préférez lire plus sur: Qu’est – ce qui détermine le cycle de vie d’un composant (graphe d’object) dans Dagger 2? Portée de l’activité Dagger2, combien de modules / composants ai-je besoin?

Et une autre remarque: si vous voulez voir quand l’object est détruit, vous pouvez appeler ceux de la méthode ensemble et le CPG fonctionnera immédiatement:

  System.runFinalization(); System.gc(); 

Si vous n’utilisez qu’une de ces méthodes, le GC s’exécutera plus tard et vous risquez d’obtenir des résultats incorrects.

    Déclarer un module distinct pour chaque Activity n’est pas une bonne idée du tout. La déclaration d’un composant distinct pour chaque Activity est encore pire. Le raisonnement derrière cela est très simple – vous n’avez pas vraiment besoin de tous ces modules / composants (comme vous l’avez déjà vu par vous-même).

    Cependant, le fait de n’avoir qu’un seul composant lié au cycle de vie de l’ Application et de l’utiliser pour l’injection dans toutes les Activities n’est pas non plus la solution optimale (c’est l’approche de votre ami). Ce n’est pas optimal parce que:

    1. Il vous limite à une seule scope ( @Singleton ou une version personnalisée)
    2. La seule scope que vous êtes limité à faire des objects injectés “application singletons”, donc des erreurs dans la scope ou l’utilisation incorrecte des objects de scope peuvent facilement causer des memory leaks globales
    3. Vous voudrez utiliser Dagger2 pour injecter dans Services aussi, mais les Services peuvent nécessiter des objects différents des Activities (par exemple, les Services n’ont pas besoin de présentateurs, n’ont pas FragmentManager , etc.). En utilisant un seul composant, vous perdez la flexibilité de définir différents graphiques d’object pour différents composants.

    Ainsi, un composant par Activity est excessif, mais un composant unique pour l’ensemble de l’application n’est pas suffisamment flexible. La solution optimale se situe entre ces deux extrêmes (comme c’est généralement le cas).

    J’utilise l’approche suivante:

    1. Composant “application” unique qui fournit des objects “globaux” (par exemple, des objects contenant un état global partagé par tous les composants de l’application). Instancié dans l’ Application .
    2. Sous-composant “Controller” du composant “application” qui fournit les objects requirejs par tous les “contrôleurs” faisant face à l’utilisateur (dans mon architecture, il s’agit des Activities et des Fragments ). Instancié dans chaque Activity et Fragment .
    3. Sous-composant “Service” du composant “application” qui fournit les objects requirejs par tous les Services . Instancié dans chaque Service .

    Vous trouverez ci-dessous un exemple de la manière dont vous pourriez mettre en œuvre la même approche.


    Modifier juillet 2017

    J’ai publié un didacticiel vidéo qui montre comment structurer le code d’dependency injection Dagger dans une application Android : tutoriel Android Dagger for Professionals .


    Modifier février 2018

    J’ai publié un cours complet sur l’dependency injection dans Android .

    Dans ce cours, j’explique la théorie de l’dependency injection et montre comment elle apparaît naturellement dans les applications Android. Ensuite, je montre comment les constructions Dagger s’intègrent dans le schéma d’dependency injection générale.

    Si vous suivez ce cours, vous comprendrez pourquoi l’idée d’avoir une définition séparée du module / composant pour chaque activité / fragment est fondamentalement erronée.

    Une telle approche amène la structure de la couche de présentation à partir d’un ensemble de classes “fonctionnel” à être reflétée dans la structure de l’ensemble de classes “Construction”, les couplant ainsi. Cela va à l’encontre de l’objective principal de l’dependency injections qui est de garder les ensembles de classes “Construction” et “Fonctionnels” disjoints.


    Champ d’application:

     @ApplicationScope @Component(modules = ApplicationModule.class) public interface ApplicationComponent { // Each subcomponent can depend on more than one module ControllerComponent newControllerComponent(ControllerModule module); ServiceComponent newServiceComponent(ServiceModule module); } @Module public class ApplicationModule { private final Application mApplication; public ApplicationModule(Application application) { mApplication = application; } @Provides @ApplicationScope Application applicationContext() { return mApplication; } @Provides @ApplicationScope SharedPreferences sharedPreferences() { return mApplication.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE); } @Provides @ApplicationScope SettingsManager settingsManager(SharedPreferences sharedPreferences) { return new SettingsManager(sharedPreferences); } } 

    Portée du contrôleur:

     @ControllerScope @Subcomponent(modules = {ControllerModule.class}) public interface ControllerComponent { void inject(CustomActivity customActivity); // add more activities if needed void inject(CustomFragment customFragment); // add more fragments if needed void inject(CustomDialogFragment customDialogFragment); // add more dialogs if needed } @Module public class ControllerModule { private Activity mActivity; private FragmentManager mFragmentManager; public ControllerModule(Activity activity, FragmentManager fragmentManager) { mActivity = activity; mFragmentManager = fragmentManager; } @Provides @ControllerScope Context context() { return mActivity; } @Provides @ControllerScope Activity activity() { return mActivity; } @Provides @ControllerScope DialogsManager dialogsManager(FragmentManager fragmentManager) { return new DialogsManager(fragmentManager); } // @Provides for presenters can be declared here, or in a standalone PresentersModule (which is better) } 

    Et puis en Activity :

     public class CustomActivity extends AppCompatActivity { @Inject DialogsManager mDialogsManager; private ControllerComponent mControllerComponent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getControllerComponent().inject(this); } private ControllerComponent getControllerComponent() { if (mControllerComponent == null) { mControllerComponent = ((MyApplication)getApplication()).getApplicationComponent() .newControllerComponent(new ControllerModule(this, getSupportFragmentManager())); } return mControllerComponent; } } 

    Informations supplémentaires sur l’dependency injection:

    Dagger 2 Scopes Demystified

    Injection de dépendance dans Android

    Certains des meilleurs exemples d’organisation de vos composants, modules et packages sont disponibles dans le référentiel Github de Google Android Architecture.

    Si vous examinez le code source, vous pouvez voir qu’il existe un seul composant d’application (avec un cycle de vie de toute la durée de l’application), puis séparer les composants d’activité et de fragment correspondant à une fonctionnalité donnée dans une application. projet. Par exemple, il existe les packages suivants:

     addedittask taskdetail tasks 

    Dans chaque paquet il y a un module, un composant, un présentateur, etc. Par exemple, dans taskdetail il y a les classes suivantes:

     TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java 

    L’avantage d’organiser de cette manière (plutôt que de regrouper toutes les activités dans un même composant ou module) est que vous pouvez tirer parti des modificateurs d’accessibilité Java et remplir le point 13 de Java effectif. En d’autres termes, les classes regroupées package et vous pouvez tirer parti des modificateurs d’accessibilité protected package-private et des package-private pour empêcher les utilisations involontaires de vos classes.

    La première option crée un composant sous-divisé pour chaque activité, l’activité pouvant créer des composants sous-regroupés qui ne fournissent que la dépendance (présentateur) pour cette activité particulière.

    La deuxième option crée un seul composant @Singleton capable de fournir aux présentateurs des dépendances non découpées, ce qui signifie que lorsque vous y accédez, vous créez à chaque fois une nouvelle instance du présentateur. (Non, cela ne crée pas une nouvelle instance tant que vous n’en demandez pas une).


    Techniquement, aucune des deux approches n’est pire. La première approche ne sépare pas les présentateurs par fonction, mais par couche.

    J’ai utilisé les deux, ils fonctionnent tous les deux et ont du sens.

    Le seul inconvénient de la première solution (si vous utilisez @Component(dependencies={...} au lieu de @Subcomponent ), c’est que vous devez vous assurer que ce n’est pas l’activité qui crée son propre module en interne, car vous ne pouvez pas Remplacez les implémentations de méthode de module par des simulacres, mais encore une fois, si vous utilisez l’injection de constructeur au lieu de l’injection de champ, vous pouvez simplement créer la classe directement avec constructeur, en lui donnant directement des simulacres.

    Votre ami a raison, vous n’avez pas vraiment besoin de créer des composants et des modules pour chaque activité. Dagger est censé vous aider à réduire le code désordonné et à rendre vos activités Android plus propres en déléguant les instanciations de classe aux modules au lieu de les instancier dans la méthode onCreate d’Activités.

    Normalement nous ferons comme ça

     public class MainActivity extends AppCompatActivity { Presenter1 mPresenter1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate. } } 

    Vous faites cela à la place

     public class MainActivity extends AppCompatActivity { @Inject Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); injectThisActivity(); } private void injectThisActivity() { MainApplication.get(this) .getMainComponent() .inject(this); }} 

    Donc écrire trop de choses en quelque sorte défait le but du poignard non? Je préfère instancier mes présentateurs dans Activités si je dois créer des modules et des composants pour chaque activité.

    En ce qui concerne vos questions sur:

    1- fuite de mémoire:

    Non, sauf si vous mettez une annotation @Singleton aux présentateurs que vous fournissez. Dagger ne créera l’object que lorsque vous faites un @Inject dans la classe cible`. Il ne créera pas les autres présentateurs dans votre scénario. Vous pouvez essayer d’utiliser Log pour voir s’ils sont créés ou non.

     @Module public class AppPresenterModule { @Provides @Singleton // < -- this will persists throughout the application, too many of these is not good Activity1Presenter provideActivity1Presentor(Context context, ...some other params){ Log.d("Activity1Presenter", "Activity1Presenter initiated"); return new Activity1PresenterImpl(context, ...some other params); } @Provides // Activity2Presenter will be provided every time you @Inject into the activity Activity2Presenter provideActivity2Presentor(Context context, ...some other params){ Log.d("Activity2Presenter", "Activity2Presenter initiated"); return new Activity2PresenterImpl(context, ...some other params); } .... Same with 48 others presenters. 

    }

    2- Vous injectez deux fois et enregistrez leur code de hachage

     //MainActivity.java @Inject Activity1Presenter mPresentation1 @Inject Activity1Presenter mPresentation2 @Inject Activity2Presenter mPresentation3 @Inject Activity2Presenter mPresentation4 //log will show Presentation2 being initiated twice @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectThisActivity(); Log.d("Activity1Presenter1", mPresentation1.hashCode()); Log.d("Activity1Presenter2", mPresentation2.hashCode()); //it will shows that both have same hash, it's a Singleton Log.d("Activity2Presenter1", mPresentation3.hashCode()); Log.d("Activity2Presenter2", mPresentation4.hashCode()); //it will shows that both have different hash, hence different objects 

    3. Non, les objects ne seront créés que lorsque vous @Inject dans les activités, au lieu de l'application init.