web-dev-qa-db-ja.com

Dagger 2モジュールとコンポーネントをどのように整理しますか?

すべてのDagger関連クラスを配置する特定のパッケージがありますか?

または、それらを注入する関連クラスの隣に置きますか? MainActivityModuleMainActivityComponentがある場合は、MainActivityと同じパッケージに入れます。

また、コンポーネントを内部クラスとして定義している人がかなりいます。 ApplicationComponentクラス内で定義されるApplicationこれは良い習慣だと思いますか?

36
ivacf

編集:これはここで真実に近いという事実から始めましょうが、これは antipattern です。MartinFowlerのData Domain Presentation Layering記事 HERE(リンクをクリック!) /MapperModulePresenterModuleを持たないことを指定します。GalleryModuleSomeFeatureModuleが必要です。すべてのマッパー、プレゼンターなどが含まれています。

賢明な方法は、コンポーネントの依存関係を使用して、各機能の元のシングルトンコンポーネントをサブスコープにすることです。これは、 "full-stack" layering separation by機能

以下に記載されているのは「アンチパターン」で、アプリケーションの最上位モジュールを「レイヤー」にカットします。これには多くの欠点があります。しないでください。しかし、あなたはそれを読んで、すべきでないことを学ぶことができます。

元のテキスト:

通常、アプリケーション全体が存在する限り、アプリ全体で使用するすべてのシングルトン依存関係を含めるために、Componentのような単一のApplicationComponentを使用します。これをApplicationクラスでインスタンス化し、他の場所からアクセスできるようにします。

私の現在のプロジェクト構造は次のとおりです。

+ 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

たとえば、私のものは次のようなものです。

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

また、フィールドにインジェクトするクラスのパッケージで保護されたフィールドにインジェクトでき​​るApplicationComponentが必要です。

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

    ...
}

私にとって、AppContextComponent@Subcomponentになりますが、実際にはそうではありません。これらは単にサブスコープを作成するための方法であり、コンポーネントを小さな部分に分割する方法ではありません。したがって、私が継承するインターフェイスは、実際にはプロビジョニングメソッドを備えた通常のinterfaceです。他の人も同じです。

public interface AppContextComponent {
    CustomApplication customApplication();

    Context applicationContext();

    AppConfig appConfig();

    PackageManager packageManager();

    AlarmManager alarmManager();
}

コンポーネントの依存関係(サブコンポーネントと同様に、サブスコープを許可します)は、複数のスコープコンポーネントを許可しません。これは、モジュールがスコープされないことも意味します。これは、Javaの複数のクラスから継承できないように、複数のスコープから継承できないためです。

スコープ外のプロバイダーは、モジュールがsingleインスタンスを保持せず、すべての注入呼び出しで新しいインスタンスを保持するようにします。スコープ依存関係を取得するには、モジュールプロバイダーメソッドにもスコープを提供する必要があります。

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

    ...
}

アプリケーションで、どこでもシングルトンコンポーネントを使用する場合、サブスコープを作成しない限り、追加のコンポーネントは必要ありません。必要に応じて、モジュールをビューやプレゼンターの完全なデータプロバイダーにすることも検討できます。

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

サブスコープを使用すると、プレゼンターの複数のインスタンスを作成して、状態を保存できます。これは、たとえば Mortar/Flow で意味があります。 各画面には独自の「パス」があり、各パスには独自のコンポーネントがあります -データを「青写真」として提供します。

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

すべてのDagger関連クラスを配置する特定のパッケージがありますか?

または、それらを注入する関連クラスの隣に置きますか? MainActivityModuleとMainActivityComponentがある場合は、MainActivityと同じパッケージに入れます。

私はその経験はあまりありませんが、私のアプローチをお見せできます。より多くの経験を積んだ人の中には、そのソリューションを改善したり、視点を提供したりできる人もいるでしょう。

私は通常、Dagger 2クラスを次のように編成します。

_- di
|
+-- ApplicationComponent class
|    
+-- modules
   |
   +-- AndroidModule class
   |
   +-- WebServiceModule class
   |
   +-- ...
   |
_
  • diパッケージには、Dagger 2および依存性注入に関連するクラスが含まれています。
  • ほとんどの場合Androidアプリケーションには通常1つのコンポーネントがあります-ここではApplicationComponentという名前です-多くのDagger 2でAndroidアプリケーションを作成していませんまだコンポーネントがあり、1つのコンポーネントだけでソリューションを見てきました。
  • modulesパッケージにはDagger 2モジュールが含まれています

アクティビティごとにモジュールを作成しません。モジュールは特定の機能をグループ化します。例えば。 SharedPreferences、EventBus(そのようなものを使用している場合)、ネットワーク接続などのインターフェースのようなシステムに強く接続されている要素は、AndroidModuleに配置できます。プロジェクトにWebServiceの重要なインターフェイスがある場合、またはそれらが多数ある場合は、WebServiceModuleにグループ化できます。たとえば、アプリケーションがネットワークの分析を担当し、ネットワークに関連する同様のタスクのための多くのインターフェイスを持っている場合、これらのインターフェイスをNetworkModuleにグループ化できます。アプリケーションが単純な場合、モジュールが1つだけになることがあります。複雑な場合、多くのモジュールを使用できます。私の意見では、単一のモジュールに多くのインターフェイスを含めるべきではありません。そのような状況がある場合、それらを別々のモジュールに分割することを検討できます。また、プロジェクト固有のビジネスロジックを別のモジュールに保持することもできます。

また、コンポーネントを内部クラスとして定義している人がかなりいます。 Applicationクラス内で定義されるApplicationComponent。これは良い習慣だと思いますか?

それが良いプラクティスか悪いプラクティスかはわかりません。そうする必要はないと思います。 Applicationクラスを拡張するクラス内にpublic static get()メソッドを作成すると、Applicationのインスタンスをシングルトンとして返します。それははるかに簡単な解決策であり、Applicationクラスのインスタンスは1つだけにする必要があります。単体テストでコンテキストをモックしたい場合は、コンテキストをパラメーターとして受け入れ、アプリケーションコードで、状況に応じてアプリケーションコンテキストまたはアクティビティコンテキストを渡すことができます。

注意してください、それは私のアプローチであり、より経験豊富な開発者の中には、プロジェクトを別のより良い方法で編成する人もいます。

16
piotr.wittchen