2017年7月25日火曜日

Android で Dagger を使う(その2 : subcomponent)


subcomponent は何?

親(parent) Component の object graph を継承し、拡張するための component

subcomponent は何のため?

アプリケーションの object graph を subgraph に分けるため

subgraph にわけるのは何のため?

- アプリケーションのさまざまな部分を互いに隔離(カプセル化)するため
- コンポーネント内で複数のスコープを使うため


つまり、subgraph に分ける必要がないのであれば、subcomponent を使う必要もなさそうです。
関係を図示すると次のようになります。
(parent)
component - modules
                            |
                            |
  ----------------------------
  |                                     |
(sibling)                  (sibling)
subcomponent       subcomponent - modules
  • subcomponent にひも付けられている object は、親および祖先の component の object に依存できる
  • subcomponent にひも付けられている object は、兄弟(sibling)subcomponent の object に依存できない
  • 親 component にひも付けられている object は、subcomponent の object に依存できない
  • 親 component の object graph は、subcomponent の object graph の subgraph になる
subcomponent は component と同じように abstract class または interface で用意します。 つけるアノテーションは @Subcomponent で、subcomponent に必要な Module を modules 属性に指定します。 modules は必須ではありません。必要な Module が無い場合は書かないこともできます。 @Subcomponent public interface MySubcomponent { } @Subcomponent(modules = MySubcomponentSpecificModule.class) public interface MySubcomponent { } @Component をつけた AppComponent interface を用意してビルドすると DaggerAppComponent が自動生成されますが、@Subcomponent をつけた interface をビルドしても何も生成されません。

subcomponent では @Subcomponent.Builder をつけた abstract class または interface を用意する必要があります。
この Builder には、引数がなく subcomponent を返すメソッドを用意する必要があります。メソッド名は build() にすることが多いです。 @Subcomponent public interface MySubcomponent { @Subcomponent.Builder interface Builder { MySubcomponent build(); } } @Subcomponent(modules = MySubcomponentSpecificModule.class) public interface MySubcomponent { @Subcomponent.Builder interface Builder { Builder mySubcomponentSpecificModule(MySubcomponentSpecificModule module) MySubcomponent build(); } } subcomponent を親の component に追加するには、親の component が取り込む Module の @Module アノテーションに subcomponents 属性で指定します。 @Module(subcomponents = MySubcomponent.class) public class MySubcomponentModule { } @Component ではなく @Module で指定するようになっているのは subcomponent の可用性・再利用性のためかなと思います。
もし subcomponentA の部分をまるっと subcomponentB に置き換えたいとなった場合、@Component で直接 subcomponentA を指定していると、subcomponentA を指定した全ての component で subcomponentB に変更する処理が必要になります。
一方、@Module で subcomponentA を指定しているなら、そこを subcomponentB に置き換えるだけで、その Module を利用している全ての component で修正は必要ありません。

よって、subcomponent を指定するための Module (特定の subcomponent 専用というよりは、subcomponent が担う処理を表現する)を別途用意するのがよいのではないかと思っています。


subcomponent を指定した Module を親の component に指定します。 @Component(modules = MySubcomponentModule.class) @Singleton public interface AppComponent { MySubcomponent.Builder mySubcomponentBuilder(); } 親の component では、@Subcomponent.Builder がついた Builder を返すメソッドを用意することができます。 これをビルドすると、DaggerAppComponent には MySubcomponent を実装した MySubcomponentImpl や、MySubcomponent.Builder を実装した MySubcomponentBuilder が生成されます。 public final class DaggerAppComponent implements AppComponent { private Provider<MySubcomponent.Builder> mySubcomponentBuilderProvider; ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.mySubcomponentBuilderProvider = new dagger.internal.Factory<MySubcomponent.Builder>() { @Override public MySubcomponent.Builder get() { return new MySubcomponentBuilder(); } }; } @Override public MySubcomponent.Builder mySubcomponentBuilder() { return mySubcomponentBuilderProvider.get(); } ... private final class MySubcomponentBuilder implements MySubcomponent.Builder { @Override public MySubcomponent build() { return new MySubcomponentImpl(this); } } private final class MySubcomponentImpl implements MySubcomponent { private MySubcomponentImpl(MySubcomponentBuilder builder) { assert builder != null; } } } mySubcomponentBuilder() で MySubcomponent.Builder が取得できるので、Builder の build() を呼んで MySubcomponent が取得できます。
あとの使い方は component と同じです。 final MySubcomponent mySubcomponent = ((MyApplication) getApplication()) .getAppComponent() .mySubcomponentBuilder() .build(); Android で Dagger を使う(その1) で AppComponent に void inject(MainActivity target) を用意しました。 @Component(modules = ApiModule.class) public interface AppComponent { void inject(MainActivity target); } MainActivity 用の subcomponent を用意して、inject() をそちらに移動してみましょう。

(MainActivity でしか使わない Module があるとか、Activity 単位でスコープを指定しないとまずいというわけでもない限り、わざわざ MainActivity 用の subcomponent を用意する必要はないと思います。) @Subcomponent public interface MainActivitySubcomponent { void inject(MainActivity target); @Subcomponent.Builder interface Builder { MainActivitySubcomponent build(); } } @Module(subcomponents = MainActivitySubcomponent.class) public class MainActivityModule { } @Component(modules = {ApiModule.class, MainActivityModule.class}) @Singleton public interface AppComponent { MainActivitySubcomponent.Builder mainActivitySubcomponentBuilder(); } public class MainActivity extends AppCompatActivity { @Inject ApiService apiService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((MyApplication) getApplication()) .getAppComponent() .mainActivitySubcomponentBuilder() .build() .inject(this); setContentView(R.layout.activity_main); } }

Scope

Subcomponent に分割する目的の一つにスコープがあります。
通常のスコープされていない紐付けでは、inject される方は新しい個別のインスンタンスをうけとります。 一方スコープされている紐付けでは、スコープのライフサイクル内では同じインスタンスを受け取ります。

Dagger では component にスコープ(@Scope アノテーションで注釈されたアノテーション)を指定することができます。 指定すると、component の実装クラスでは同じスコープが指定された型のインスタンスを保持するようになります。これにより同じインスタンスを再利用することができます。

標準スコープが @Singleton です。スコープなので Singleton アノテーションの定義には @Scope がついています。

subcomponent は親および祖先と同じスコープにすることはできません。

これはだめ @Component(modules = {ApiModule.class, MainActivityModule.class}) @Singleton public interface AppComponent { MainActivitySubcomponent.Builder mainActivitySubcomponentBuilder(); } @Subcomponent @Singleton public interface MainActivitySubcomponent { ... } 互いに到達できない2つの subcomponent に同じスコープを指定することはできます(同じスコープアノテーションを使用しても、実際のスコープインスタンスは異なります)。

これはできる @Component(modules = {...}) @Singleton public interface AppComponent { MainActivitySubcomponent.Builder mainActivityComponent(); MainActivity2Subcomponent.Builder mainActivity2Component(); } @Subcomponent @ActivityScope public interface MainActivitySubcomponent { ... } @Subcomponent @ActivityScope public interface MainActivity2Subcomponent { ... } @Scope @Documented @Retention(RUNTIME) public @interface ActivityScope { }


0 件のコメント:

コメントを投稿