- Android で Dagger を使う(その1)
- Android で Dagger を使う(その2 : subcomponent)
- Android で Dagger を使う(その3 : Android Support)
subcomponent は何?
親(parent) Component の object graph を継承し、拡張するための componentsubcomponent は何のため?
アプリケーションの 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
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 件のコメント:
コメントを投稿