web-dev-qa-db-fra.com

Dague - Faut-il créer chaque composant et module pour chaque activité / fragment?

Je travaille avec dagger2 depuis un moment. Et je me suis demandé s'il fallait créer un propre composant/module pour chaque activité/fragment. S'il vous plaît aidez-moi à clarifier ceci:

Par exemple, nous avons une application, qui compte 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 normalement organiser le code comme suit:

  1. Créez un AppComponent et un AppModule qui fourniront tous les objets 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 vais les mettre en tant que classes statiques dans la classe d'activité:

    @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 en œuvre cela.

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éer 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 à créer de composants et de modules pour chaque activité. Je pense que l'idée de mes amis n'est absolument pas bonne du tout, mais s'il vous plaît Corrigez-moi si je me trompe. Voici les raisons:

  1. Beaucoup de fuites de mémoire :

    • 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 reste 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. Il faudra beaucoup de temps pour que l'application s'initialise (car elle doit créer de nombreux présentateurs, objets, ...)

Désolé pour un long post, mais aidez-moi à 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 fuites de mémoire, sauf s'il met les méthodes Portée en oeuvre. (Par exemple, @Singleton ou @UserScope, ...)
  2. Nous pouvons créer de nombreux présentateurs si la méthode Provides n'a pas de portée. (Donc, mon deuxième point est faux, aussi)
  3. La dague créera les présentateurs seulement quand ils sont nécessaires. (Donc, l'application ne prendra pas longtemps pour initialiser, j'ai été confondu par Lazy Injection)

Donc, toutes les raisons que j'ai mentionnées 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 insère tous les présentateurs dans le module/composant. (Cela viole principe de ségrégation d'interface , peut-être responsabilité unique principe, aussi).

  2. Lorsque nous créons un composant Scope, nous savons à quel moment il est créé et quand il est détruit, ce qui est un avantage énorme pour éviter les fuites de mémoire. Ainsi, pour chaque activité, nous devons créer un composant avec un @ActivityScope. Imaginons, avec l'implémentation de mes amis, que nous avons oublié de mettre une certaine portée dans la méthode fournisseur => des fuites de mémoire se produiront.

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

Préférez en savoir plus sur: Qu'est-ce qui détermine le cycle de vie d'un composant (graphe d'objet) dans Dagger 2?portée de l'activité de Dagger2, combien de modules/composants ai-je besoin?

Et encore une remarque: si vous voulez voir quand les objets sont détruits, vous pouvez appeler ceux de la méthode ensemble et le GC s'exécutera immédiatement:

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

Si vous utilisez une seule de ces méthodes, GC s'exécutera plus tard et vous risquez d'obtenir des résultats erronés.

78
Mr Mike

Déclarer un module distinct pour chaque Activity n’est pas une bonne idée du tout. Déclarer 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, avoir un seul composant lié au cycle de vie de Application et l'utiliser pour l'injection dans tous 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. Cela vous limite à un seul domaine (@Singleton ou un personnalisé)
  2. La seule portée à laquelle vous êtes restreint transforme les objets injectés en "singletons d'application". Par conséquent, des erreurs de définition ou une utilisation incorrecte des objets définis peuvent facilement provoquer des fuites de mémoire globales.
  3. Vous voudrez peut-être aussi utiliser Dagger2 pour injecter dans Services, mais Services peut nécessiter des objets différents de Activities (par exemple, Services n'a pas besoin de les présentateurs, n'ont pas FragmentManager, etc.). En utilisant un seul composant, vous perdez la possibilité de définir différents graphiques d'objet pour différents composants.

Ainsi, un composant par Activity est une surcharge, mais un seul composant pour l’ensemble de l’application n’est pas assez flexible. La solution optimale se situe entre ces deux extrêmes (comme d'habitude).

J'utilise l'approche suivante:

  1. Composant "d'application" unique qui fournit des objets "globaux" (par exemple, des objets contenant un état global partagé entre tous les composants de l'application). Instancié dans Application.
  2. "Contrôleur" sous-composant du composant "application" qui fournit les objets requis par tous les "contrôleurs" destinés aux utilisateurs (dans mon architecture, il s'agit de Activities et Fragments). Instancié dans chaque Activity et Fragment.
  3. "Service" sous-composant du composant "application" qui fournit les objets requis par tous les Services. Instancié dans chaque Service.

Voici un exemple de mise en œuvre de la même approche.


Éditer juillet 2017

J'ai publié un didacticiel vidéo qui montre comment structurer le code d'injection de dépendance de poignard dans Android: didacticiel Android Dagger for Professionals) .


Edition de février 2018

J'ai publié un cours complet sur l'injection de dépendance sous Android .

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

Si vous suivez ce cours, vous comprendrez pourquoi l'idée de définir séparément le module/composant pour chaque activité/fragment est fondamentalement erronée.

Une telle approche fait que la structure de la couche de présentation de l'ensemble "fonctionnel" de classes est reflétée dans la structure de l'ensemble "de construction", en les couplant ainsi. Cela va à l’encontre de l’objectif principal de l’injection de dépendance, qui est de garder les ensembles de classes "Construction" et "Fonctionnel" 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 dans 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 complémentaires sur l'injection de dépendance:

Dagger 2 Scopes Demystified

Injection de dépendance sous Android

74
Vasiliy

Vous trouverez quelques-uns des meilleurs exemples d'organisation de vos composants, modules et packages dans le référentiel Google Android Architecture Github ici . .

Si vous examinez le code source à cet endroit, vous constaterez qu’il existe un seul composant d’application (avec un cycle de vie de la durée de l’application entière), puis des composants séparés pour l’activité pour l’activité et le fragment correspondant à une fonctionnalité donnée dans une application donnée. 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 de l’organisation de cette manière (plutôt que de regrouper toutes les activités dans un composant ou un module) est que vous pouvez tirer parti des modificateurs d’accessibilité Java et remplir le critère efficace Java 13. En d’autres termes, les classes regroupées fonctionnellement seront dans le même package et vous pourrez tirer parti de protected et de package-privatemodificateurs d'accessibilité pour empêcher usages imprévus de vos cours.

14
David Rawson

La première option crée un composant sous-coté pour chaque activité, l’activité pouvant créer des composants sous-cotés fournissant uniquement la dépendance (présentateur) pour cette activité particulière.

La deuxième option crée un seul @Singleton composant capable de fournir aux présentateurs des dépendances non délimité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 approche n'est pire que l'autre. La première approche ne sépare pas les présentateurs par caractéristique, mais par couche.

J'ai utilisé les deux, ils fonctionnent tous les deux et ont un sens.

Le seul inconvénient de la première solution (si vous utilisez @Component(dependencies={...} au lieu de @Subcomponent) est que vous devez vous assurer que ce n'est pas l'Activité qui crée son propre module en interne, car vous ne pourrez pas remplacer les implémentations de méthodes de modules par des mocks. Là encore, 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 imitations.

3
EpicPandaForce