web-dev-qa-db-fra.com

Comment organisez-vous vos modules et composants Dagger 2?

Avez-vous un package spécifique dans lequel vous mettez toutes les classes liées à Dagger?

Ou les placez-vous à côté de la classe appropriée qu'ils injectent, par exemple si vous avez un MainActivityModule et MainActivityComponent, vous les mettez dans le même package que votre MainActivity.

De plus, j'ai vu pas mal de gens définir des composants comme des classes internes, par exemple un ApplicationComponent défini dans la classe Application. Pensez-vous que c'est une bonne pratique?

36
ivacf

EDIT: Permettez-moi de commencer par le fait que cela est proche de la vérité ici, mais il s'agit d'un anti-modèle tel que décrit par le Martin Fowler's Data Domain Presentation Layering article ICI (CLIQUEZ SUR LE LIEN!) , qui spécifie que vous ne devriez pas avoir un MapperModule et un PresenterModule, vous devriez avoir un GalleryModule et un SomeFeatureModule qui contient tous les mappeurs, présentateurs, etc.

La route intelligente pour y parvenir est d'utiliser les dépendances des composants pour subscoper votre composant singleton d'origine pour chaque fonctionnalité que vous avez. Voici ce que j'ai décrit ( superposition "pleine pile" , séparation par fonctionnalités.

Celui qui est écrit ci-dessous est l '"anti-modèle", où vous coupez les modules de niveau supérieur de votre application en "couches". Cela présente de nombreux inconvénients. Ne le fais pas. Mais vous pouvez le lire et savoir quoi ne pas faire.

TEXTE ORIGINAL:

Normalement, vous utiliseriez un seul Component comme un ApplicationComponent pour contenir toutes les dépendances singleton que vous utilisez dans l'application tant que l'application entière existe. Vous instancieriez cela dans votre classe Application et le rendriez accessible ailleurs.

La structure du projet pour moi est actuellement:

+ injection
|- components
   |-- ApplicationComponent.Java
|- modules
   |- data
      |-- DbMapperModule.Java
      |-- ...
   |- domain
      |-- InteractorModule.Java
      |-- ...
   |- presentation
      |-- ...
   |- utils
      |-- ...
|- scope
|- subcomponents
   |- data
      |-- ...
   |- domain
      |-- DbMapperComponent.Java
      |-- ...
   |- presentation
      |-- ...
   |- utils
      |-- ...
   |-- AppContextComponent.Java
   |-- AppDataComponent.Java
   |-- AppDomainComponent.Java
   |-- AppPresentationComponent.Java
   |-- AppUtilsComponent.Java

Par exemple, le mien est comme ceci:

public enum Injector {
    INSTANCE;
    private ApplicationComponent applicationComponent;

    private Injector() {
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }

    ApplicationComponent initializeApplicationComponent(CustomApplication customApplication) {
        AppContextModule appContextModule = new AppContextModule(customApplication);
        RealmModule realmModule = new RealmModule(customApplication.getRealmHolder());
        applicationComponent = DaggerApplicationComponent.builder()
                .appContextModule(appContextModule)
                .realmModule(realmModule)
                .build();
        return applicationComponent;
    }
}

Et vous avez besoin d'un ApplicationComponent qui peut injecter dans les champs protégés par package de la classe à laquelle vous souhaitez injecter les champs.

@Singleton
@Component(modules = {
        AppContextModule.class,
        DbMapperModule.class,
        DbTaskModule.class,
        RealmModule.class,
        RepositoryModule.class,
        InteractorModule.class,
        ManagerModule.class,
        ServiceModule.class,
        PresenterModule.class,
        JobManagerModule.class,
        XmlPersisterModule.class
})
public interface ApplicationComponent
        extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
    void inject(CustomApplication customApplication);

    void inject(DashboardActivity dashboardActivity);

    ...
}

Pour moi, AppContextComponent serait un @Subcomponent, mais ce n'est pas vraiment ce que cela signifie. Ce n'est qu'un moyen de créer un sous-champ, et non un moyen de couper votre composant en parties plus petites. L'interface dont j'hérite n'est donc en fait qu'un interface normal avec des méthodes de provision. Pareil pour les autres.

public interface AppContextComponent {
    CustomApplication customApplication();

    Context applicationContext();

    AppConfig appConfig();

    PackageManager packageManager();

    AlarmManager alarmManager();
}

Les dépendances de composants (qui vous permettent de sous-étendre, tout comme les sous-composants) n'autorisent pas plusieurs composants de portée, ce qui signifie également que vos modules seraient hors portée. Cela est dû au fait que vous ne pouvez pas hériter de plusieurs étendues, tout comme vous ne pouvez pas hériter de plusieurs classes en Java.

Les fournisseurs non étendus font en sorte que le module ne conserve pas une instance single mais une nouvelle à chaque appel d'injection. Pour obtenir des dépendances étendues, vous devez également fournir la portée des méthodes du fournisseur de module.

@Module
public class InteractorModule {
    @Provides
    @Singleton
    public LeftNavigationDrawerInteractor leftNavigationDrawerInteractor() {
        return new LeftNavigationDrawerInteractorImpl();
    }

    ...
}

Dans une application, si vous utilisez des composants Singleton partout, vous n'aurez pas besoin de plus de composants, sauf si vous créez des sous-étendues. Si vous le souhaitez, vous pouvez même envisager de faire de vos modules un fournisseur de données complet pour vos vues et vos présentateurs.

@Component(dependencies = {ApplicationComponent.class}, modules = {DetailActivityModule.class}) 
@ActivityScope
public interface DetailActivityComponent extends ApplicationComponent {
    DataObject data();

    void inject(DetailActivity detailActivity);
}

@Module
public class DetailActivityModule {
    private String parameter;

    public DetailActivityModule(String parameter) {
        this.parameter = parameter;
    }

    @Provides
    public DataObject data(RealmHolder realmHolder) {
        Realm realm = realmHolder.getRealm();
        return realm.where(DataObject.class).equalTo("parameter", parameter).findFirst();
    }
}

La sous-étendue vous permet d'avoir plusieurs instances de votre présentateur, qui peuvent ensuite stocker l'état. Cela a du sens, par exemple Mortier/Flux , où chaque écran a son propre "chemin", et chaque chemin a sa propre composante - pour fournir les données sous forme de "plan directeur".

public class FirstPath
        extends BasePath {
    public static final String TAG = " FirstPath";

    public final int parameter;

    public FirstPath(int parameter) {
        this.parameter = parameter;
    }

    //...

    @Override
    public int getLayout() {
        return R.layout.path_first;
    }

    @Override
    public FirstViewComponent createComponent() {
        FirstPath.FirstViewComponent firstViewComponent = DaggerFirstPath_FirstViewComponent.builder()
                .applicationComponent(InjectorService.obtain())
                .firstViewModule(new FirstPath.FirstViewModule(parameter))
                .build();
        return firstViewComponent;
    }

    @Override
    public String getScopeName() {
        return TAG + "_" + parameter;
    }

    @ViewScope //needed
    @Component(dependencies = {ApplicationComponent.class}, modules = {FirstViewModule.class})
    public interface FirstViewComponent
            extends ApplicationComponent {
        String data();

        FirstViewPresenter firstViewPresenter();

        void inject(FirstView firstView);

        void inject(FirstViewPresenter firstViewPresenter);
    }

    @Module
    public static class FirstViewModule {
        private int parameter;

        public FirstViewModule(int parameter) {
            this.parameter = parameter;
        }

        @Provides
        public String data(Context context) {
            return context.getString(parameter);
        }

        @Provides
        @ViewScope //needed to preserve scope
        public FirstViewPresenter firstViewPresenter() {
            return new FirstViewPresenter();
        }
    }

    public static class FirstViewPresenter
            extends ViewPresenter<FirstView> {
        public static final String TAG = FirstViewPresenter.class.getSimpleName();

        @Inject
        String data;

        public FirstViewPresenter() {
            Log.d(TAG, "First View Presenter created: " + toString());
        }

        @Override
        protected void onEnterScope(MortarScope scope) {
            super.onEnterScope(scope);
            FirstViewComponent firstViewComponent = scope.getService(DaggerService.TAG);
            firstViewComponent.inject(this);
            Log.d(TAG, "Data [" + data + "] and other objects injected to first presenter.");
        }

        @Override
        protected void onSave(Bundle outState) {
            super.onSave(outState);
            FirstView firstView = getView();
            outState.putString("input", firstView.getInput());
        }

        @Override
        protected void onLoad(Bundle savedInstanceState) {
            super.onLoad(savedInstanceState);
            if(!hasView()) {
                return;
            }
            FirstView firstView = getView();
            if(savedInstanceState != null) { //needed check
                firstView.setInput(savedInstanceState.getString("input"));
            }
        }

        public void goToNextActivity() {
            FirstPath firstPath = Path.get(getView().getContext());
            if(firstPath.parameter != R.string.hello_world) {
                Flow.get(getView()).set(new FirstPath(R.string.hello_world));
            } else {
                Flow.get(getView()).set(new SecondPath());
            }
        }
    }
}
26
EpicPandaForce

Avez-vous un package spécifique dans lequel vous mettez toutes les classes liées à Dagger?

Ou les placez-vous à côté de la classe appropriée qu'ils injectent, par exemple si vous avez un MainActivityModule et MainActivityComponent, vous les placez dans le même package que votre MainActivity.

Je n'ai pas beaucoup d'expérience avec ça, mais je peux vous montrer mon approche. Peut-être que certaines personnes plus expérimentées peuvent améliorer cette solution ou donner leur point de vue.

J'organise généralement des cours Dagger 2 comme ça:

- di
|
+-- ApplicationComponent class
|    
+-- modules
   |
   +-- AndroidModule class
   |
   +-- WebServiceModule class
   |
   +-- ...
   |
  • di le paquet contient des classes liées à Dagger 2 et à l'injection de dépendances.
  • dans la plupart des cas Android a généralement un composant - ici, il s'appelle ApplicationComponent - je n'ai pas créé d'application Android avec de nombreux Dagger 2 composants encore et j'ai vu des solutions avec un seul composant.
  • modules le paquet contient les modules Dagger 2

Je ne crée pas de module par activité. Les modules regroupent des fonctionnalités spécifiques. Par exemple. les éléments fortement liés au système comme l'interface pour SharedPreferences, EventBus (si vous utilisez quelque chose comme ça), la connectivité réseau, etc. peuvent être situés dans AndroidModule. Si votre projet a des interfaces importantes pour WebService ou s'il y en a beaucoup, vous pouvez les regrouper dans WebServiceModule. Si votre application est par exemple chargée d'analyser le réseau et possède de nombreuses interfaces pour des tâches similaires liées au réseau, vous pouvez regrouper ces interfaces dans NetworkModule. Lorsque votre application est simple, il peut arriver que vous n'ayez qu'un seul module. Quand c'est compliqué, vous pouvez avoir plusieurs modules. À mon avis, vous ne devriez pas avoir beaucoup d'interfaces dans un seul module. Quand il y a une telle situation, vous pouvez envisager de les diviser en modules séparés. Vous pouvez également conserver une logique métier spécifique à votre projet dans un module distinct.

De plus, j'ai vu pas mal de gens définir des composants comme des classes internes, par exemple un ApplicationComponent défini dans la classe Application. Pensez-vous que c'est une bonne pratique?

Je ne sais pas si c'est une bonne ou une mauvaise pratique. Je pense que ce n'est pas nécessaire. Vous pouvez créer une méthode publique statique get() à l'intérieur de la classe étendant la classe Application, qui renverra l'instance de Application comme singleton. C'est une solution beaucoup plus simple et nous ne devrions avoir qu'une seule instance d'une classe Application. Si nous voulons simuler le contexte dans un test unitaire, nous pouvons accepter le contexte comme paramètre et dans un code d'application, passer le contexte d'application ou le contexte d'activité selon la situation.

Veuillez noter que c'est juste mon approche et certains développeurs plus expérimentés peuvent organiser leurs projets d'une manière différente et meilleure.

16
piotr.wittchen