Dagger 2を使用した@ActivityScope

こんにちは、Habr! ActivityScopeを作成した経験を共有したいと思います。 私の意見では、インターネットで見たこれらの例は、完全ではなく、無関係で、人工的なものではなく、実際の開発のニュアンスを考慮していません。



この記事では、読者が既にDagger 2に精通しており、コンポーネント、モジュール、インジェクション、オブジェクトグラフとは何か、またそれらがどのように連携するかを理解していることを前提としています。 ここでは、まず、ActivityScopeの作成と、それをフラグメントにリンクする方法に集中します。



それでは、行きましょう...スコープとは何ですか?







スコープは、独自のライフサイクルを持つ特定のオブジェクトセットを保存できるDagger 2メカニズムです。 言い換えると、スコープは、開発者に依存する独自の存続期間を持つオブジェクトのグラフです。



デフォルトでは、Dagger 2はそのままでjavax.inject.Singletonスコープのサポートを提供します 。 原則として、このスコープ内のオブジェクトは、アプリケーションのインスタンスが存在する限り存在します。



さらに、独自の追加スコープを作成する可能性に制限はありません。 カスタムスコープの良い例は、ユーザーがアプリケーションで承認されている限りオブジェクトが存在するUserScopeです。 ユーザーセッションが終了するか、ユーザーがアプリケーションを明示的に終了するとすぐに、オブジェクトグラフは破棄され、次の承認時に再作成されます。 このようなスコープでは、特定のユーザーに関連付けられたオブジェクトを保存すると便利であり、他のユーザーにとっては意味がありません。 たとえば、特定のユーザーのアカウントのリストを表示できるAccountManagerなど。







図は、アプリケーションのシングルトンおよびユーザースコープのライフサイクルの例を示しています。





うまくいけば、スコープが少し整理されます。



次に、 ActivityScopeの例に戻ります。 実際のAndroidアプリケーションでは、ActivityScopeは非常に便利です。 もちろんそうです! さまざまなフラグメントのかかと、アダプターの束、ヘルパー、プレゼンターなど、クラスの束で構成される複雑な画面を想像するだけで十分です。 この場合、一般的なはずのビジネスロジックのモデルおよび/またはクラスをそれらの間で「手探りする」ことが理想的です。



この問題を解決するための3つのオプションがあります。



  1. 自作のシングルトン、アプリケーションクラス、または静的変数を使用して、参照を共通オブジェクトに転送します。 OOPとSOLIDの原則に違反しているため、コードが混乱し、読みにくく、サポートされていないため、このアプローチは絶対に好きではありません。



  2. セッターまたはコンストラクターを介して、アクティビティから必要なクラスにオブジェクトを独立して転送します。 このアプローチの欠点は、代わりに新しい機能の作成に集中できる場合に、ルーチンコードを作成するコストです。



  3. Dagger 2を使用して、アプリケーションの必要な場所に共有オブジェクトを注入します。 この場合、テンプレートコードを書く時間を無駄にすることなく、2番目のアプローチのすべての利点を得ることができます。 実際、ミドルウェアの記述をライブラリに移行しています。


Dagger 2を使用してActivityScopeを作成および使用する手順を見てみましょう。



したがって、カスタムスコープを作成するには、以下が必要です。





デモアプリケーションのインターフェイスは、 ActivityActivityBの 2つの画面と、 SharedFragmentアクティビティで使用される共通のフラグメントで構成されます。







アプリケーションには、 SingletonActivityScopeの 2つのスコープがあります。



従来、ビンはすべて3つのグループに分類できます。





各Beanは、作成時に一意のIDを取得します。 これにより、スコープが意図したとおりに機能するかどうかを明確に理解できるようになります。これは、各新しいBeanインスタンスのIDが以前のものと異なるためです。







したがって、アプリケーションにはオブジェクトの3つのグラフ(3つのコンポーネント)があります









実装に移りましょう。 開始するには、Dagger 2をプロジェクトに接続します。 これを行うには、ルートbuild.gradleandroid-aptプラグインを接続します...



buildscript { //... dependencies { //... classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } }
      
      





app / build.gradleの Dagger 2自体



 dependencies { compile 'com.google.dagger:dagger:2.7' apt 'com.google.dagger:dagger-compiler:2.7' }
      
      





次に、シングルトンを提供するモジュールを宣言します



 @Module public class SingletonModule { @Singleton @Provides SingletonBean provideSingletonBean() { return new SingletonBean(); } }
      
      





およびシングルトンコンポーネント:



 @Singleton @Component(modules = SingletonModule.class) public interface SingletonComponent { }
      
      





インジェクターを作成します。これは、ダガー2ではなく、制御するアプリケーション内の唯一のシングルトンであり、ダガーのシングルトンスコープを保持し、インジェクションを担当します。



 public final class Injector { private static final Injector INSTANCE = new Injector(); private SingletonComponent singletonComponent; private Injector() { singletonComponent = DaggerSingletonComponent.builder() .singletonModule(new SingletonModule()) .build(); } public static SingletonComponent getSingletonComponent() { return INSTANCE.singletonComponent; } }
      
      





ActivityScopeを宣言します。 スコープを宣言するには、スコープの名前で注釈を作成し、 javax.inject.Scope注釈でマークする必要があります



 @Scope public @interface ActivityScope { }
      
      





Beanをモジュールにグループ化:共有およびアクティビティ用



 @Module public class ModuleA { @ActivityScope @Provides BeanA provideBeanA() { return new BeanA(); } } @Module public class ModuleB { @ActivityScope @Provides BeanB provideBeanB() { return new BeanB(); } } @Module public class SharedModule { @ActivityScope @Provides SharedBean provideSharedBean() { return new SharedBean(); } }
      
      





アクティビティの対応するコンポーネントを宣言します。 別のコンポーネントのオブジェクトを含むコンポーネントを実装するには、 サブコンポーネントコンポーネントの依存関係の 2つの方法があります 。 前者の場合、子コンポーネントは親コンポーネントのすべてのオブジェクトに自動的にアクセスできます。 2番目-親コンポーネントでは、子にエクスポートするオブジェクトのリストを明示的に指定する必要があります。 私の意見では、1つのアプリケーションのフレームワークでは、最初のオプションを使用する方が便利です。



 @ActivityScope @Subcomponent(modules = {ModuleA.class, SharedModule.class}) public interface ComponentActivityA { void inject(ActivityA activity); void inject(SharedFragment fragment); } @ActivityScope @Subcomponent(modules = {ModuleB.class, SharedModule.class}) public interface ComponentActivityB { void inject(ActivityB activity); void inject(SharedFragment fragment); }
      
      





作成されたサブコンポーネントで、注入ポイントを宣言します。 この例では、 アクティビティSharedFragmentという 2つのポイントがあります 。 彼らはSharedBean共有Beanを共有します



サブコンポーネントインスタンスは、サブコンポーネントモジュールのオブジェクトを既存のグラフに追加することにより、親コンポーネントから取得されます。 この例では、親コンポーネントはSingletonComponentで 、サブコンポーネントを作成するためのメソッドを追加します。



 @Singleton @Component(modules = SingletonModule.class) public interface SingletonComponent { ComponentActivityA newComponent(ModuleA a, SharedModule shared); ComponentActivityB newComponent(ModuleB b, SharedModule shared); }
      
      





以上です。 インフラストラクチャ全体の準備が完了し、宣言されたコンポーネントをインスタンス化し、依存関係を注入します。 フラグメントから始めましょう。



フラグメントは2つの異なるアクティビティの内部ですぐに使用されるため、フラグメントが配置されているアクティビティに関する特定の詳細を知る必要はありません。 ただし、スコープのオブジェクトのグラフにアクセスするには、アクティビティコンポーネントにアクセスする必要があります。 この「問題」を解決するために、 Inversion of Controlパターンを使用して、アクティビティとの相互作用を構築する中間のInjectorProviderインターフェイスを作成します。



 public class SharedFragment extends Fragment { @Inject SharedBean shared; @Inject SingletonBean singleton; //… @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof InjectorProvider) { ((InjectorProvider) context).inject(this); } else { throw new IllegalStateException("You should provide InjectorProvider"); } } public interface InjectorProvider { void inject(SharedFragment fragment); } }
      
      





アクティビティ内のActivityScopeレベルのコンポーネントをインスタンス化し、アクティビティとその中に含まれるフラグメントを注入することは残ります



 public class ActivityA extends AppCompatActivity implements SharedFragment.InjectorProvider { @Inject SharedBean shared; @Inject BeanA a; @Inject SingletonBean singleton; ComponentActivityA component = Injector.getSingletonComponent() .newComponent(new ModuleA(), new SharedModule()); //... @Override public void inject(SharedFragment fragment) { component.inject(this); component.inject(fragment); } }
      
      





主なポイントを繰り返します。





一見すると、このような単純なタスクを実装するには、大量の接続コードを記述する必要があるように思えるかもしれません。 デモアプリケーションでは、「作業」を実行するクラス(ビン、フラグメント、およびアクティビティ)の数は、「接続」ダガークラスの数とほぼ同等です。 ただし:





» GitHubで利用可能なデモプロジェクト



すべての短剣と幸せなコーディング! :)



All Articles