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 が無い場合は書かないこともできます。
  1. @Subcomponent  
  2. public interface MySubcomponent {  
  3. }  
  1. @Subcomponent(modules = MySubcomponentSpecificModule.class)  
  2. public interface MySubcomponent {  
  3. }  
@Component をつけた AppComponent interface を用意してビルドすると DaggerAppComponent が自動生成されますが、@Subcomponent をつけた interface をビルドしても何も生成されません。

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

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


subcomponent を指定した Module を親の component に指定します。
  1. @Component(modules = MySubcomponentModule.class)  
  2. @Singleton  
  3. public interface AppComponent {  
  4.   
  5.     MySubcomponent.Builder mySubcomponentBuilder();  
  6. }  
親の component では、@Subcomponent.Builder がついた Builder を返すメソッドを用意することができます。 これをビルドすると、DaggerAppComponent には MySubcomponent を実装した MySubcomponentImpl や、MySubcomponent.Builder を実装した MySubcomponentBuilder が生成されます。
  1. public final class DaggerAppComponent implements AppComponent {  
  2.   private Provider<MySubcomponent.Builder> mySubcomponentBuilderProvider;  
  3.   
  4.   ...  
  5.   
  6.   @SuppressWarnings("unchecked")  
  7.   private void initialize(final Builder builder) {  
  8.   
  9.     this.mySubcomponentBuilderProvider =  
  10.         new dagger.internal.Factory<MySubcomponent.Builder>() {  
  11.           @Override  
  12.           public MySubcomponent.Builder get() {  
  13.             return new MySubcomponentBuilder();  
  14.           }  
  15.         };  
  16.   }  
  17.   
  18.   @Override  
  19.   public MySubcomponent.Builder mySubcomponentBuilder() {  
  20.     return mySubcomponentBuilderProvider.get();  
  21.   }  
  22.   
  23.   ...  
  24.   
  25.   private final class MySubcomponentBuilder implements MySubcomponent.Builder {  
  26.     @Override  
  27.     public MySubcomponent build() {  
  28.       return new MySubcomponentImpl(this);  
  29.     }  
  30.   }  
  31.   
  32.   private final class MySubcomponentImpl implements MySubcomponent {  
  33.     private MySubcomponentImpl(MySubcomponentBuilder builder) {  
  34.       assert builder != null;  
  35.     }  
  36.   }  
  37. }  
mySubcomponentBuilder() で MySubcomponent.Builder が取得できるので、Builder の build() を呼んで MySubcomponent が取得できます。
あとの使い方は component と同じです。
  1. final MySubcomponent mySubcomponent = ((MyApplication) getApplication())  
  2.         .getAppComponent()  
  3.         .mySubcomponentBuilder()  
  4.         .build();  
Android で Dagger を使う(その1) で AppComponent に void inject(MainActivity target) を用意しました。
  1. @Component(modules = ApiModule.class)    
  2. public interface AppComponent {    
  3.     
  4.     void inject(MainActivity target);    
  5. }  
MainActivity 用の subcomponent を用意して、inject() をそちらに移動してみましょう。

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


Scope

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

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

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

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

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

これはできる
  1. @Component(modules = {...})  
  2. @Singleton  
  3. public interface AppComponent {  
  4.   
  5.     MainActivitySubcomponent.Builder mainActivityComponent();  
  6.     MainActivity2Subcomponent.Builder mainActivity2Component();  
  7. }  
  8.   
  9. @Subcomponent  
  10. @ActivityScope  
  11. public interface MainActivitySubcomponent {  
  12.     ...  
  13. }  
  14.   
  15. @Subcomponent  
  16. @ActivityScope  
  17. public interface MainActivity2Subcomponent {  
  18.     ...  
  19. }  
  20.   
  21. @Scope  
  22. @Documented  
  23. @Retention(RUNTIME)  
  24. public @interface ActivityScope {  
  25. }  



0 件のコメント:

コメントを投稿