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:
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, ....)
}
Créer ActivityScope:
@Scope
@Documented
@Retention(value=RUNTIME)
public @interface ActivityScope {
}
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:
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.
}
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:
Beaucoup de fuites de mémoire :
Que se passe-t-il si je veux créer deux instances d'une activité? (comment peut-il créer deux présentateurs)
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.
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:
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).
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.
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:
@Singleton
ou un personnalisé)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:
Application
.Activities
et Fragments
). Instancié dans chaque Activity
et Fragment
.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:
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-private
modificateurs d'accessibilité pour empêcher usages imprévus de vos cours.
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.