Scoped bindings、またはライフサイクルごとのシングルトン
@Singleton
一例としてRetrofitのRestAdapterや得られたClientは(今はキャッシュされているかもですが)生成に時間がかかるので自分でキャッシュしてください、とされていました。DIでこれを実現するには@Singleton
を使います。
Dagger 2の場合は、Moduleの@Provides
メソッドもしくはSingletonにしたいclassの定義と、(Sub)Componentの定義に@Singleton
をつけると実現できます。
@Module
public class MyApplicationModule {
...
@Provides
@Singleton
ApiClient providesApiClient(RestAdapter restAdapter) {
restAdapter.create(ApiClient.class);
}
...
}
@Singleton
@Component(modules = ...)
public interface MyApplicationComponent {
void inject(MyApplication target);
}
カスタムの@Scope
実際には、同じActivityやFragmentの内側でだけSingletonにしたいものもあります。Dagger 2ではカスタムのAnnotationとSubcomponent(多分Component Dependenciesでもおk)を作成することで、自由にScopeを切ることができます。
Subcomponentは、親ComponentからModuleが追加された別のComponentを作る仕組みです(Dagger 1でいうplus())。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
@ActivityScope
@Subcomponent(modules = ...)
public interface MyActivityComponent {
void inject(MyActivity target);
}
@Singleton
@Component(modules = {MyActivityModule.class, ...})
public interface MyApplicationComponent {
// Moduleのnewにパラメータがなければ引数に書かなくてもおkです
MyActivityComponent createActivityComponent(MyActivityModule myActivityModule);
...
}
@ActivityScope // これがついているものはMyActivityComponentもしくはその子Componentでしかinjectできないです
public class MyViewModel {
...
}
Androidでの実装方法
Scopeを切るには、親Componentを使って子Componentを作らないといけないのですが、つまり親のライフサイクルにComponentを持たせて、子のほうで取ってくる必要があります。
public class MyApplication extends Application {
private MyApplicationComponent mComponent;
public void onCreate() {
mComponent = DaggerMyApplicationComponent.create();
mComponent.inject(this);
super.onCreate();
}
public MyApplicationComponent getComponent() {
return mComponent;
}
...
}
public class MyActivity extends AppCompatActivity {
private MyActivityComponent mComponent;
public void onCreate(Bundle savedInstanceState) {
MyApplicationComponent applicationComponent = ((MyApplication) getApplicationContext()).getComponent();
mComponent = applicationComponent.createActivityComponent(new MyActivityModule(this));
mComponent.inject(this);
super.onCreate(savedInstanceState);
}
public MyActivityComponent getComponent() {
return mComponent;
}
...
}
(Fragment用のComponentはいらないという人が多いかも?)
public class MyFragment extends Fragment {
private MyFragmentComponent mComponent;
public void onCreate(Bundle savedInstanceState) {
MyActivityComponent activityComponent = ((MyActivity) getActivity()).getComponent();
activityComponent.createFragmentComponent(new MyFragmentModule(this)).inject(this);
super.onCreate(savedInstanceState);
}
...
}
またboilerplateか・・・→ライブラリでちょっと楽にする
この程度でできるので自前で書いちゃってもよいかもしれませんが、ActivityやFragmentの数が増えてきた時に、アレコレ変更するのがだるい感じになりますよね・・。と思って、Componentの生成ロジックをhelperとしてまとめたり、getComponent()用のinterfaceを定義したりしていたのですが、他のプロジェクトでも同じことやると考えるとめんどくさくなったのでライブラリに切り出しました。
最初のセットアップを終わればあとはonCreate()で下記のように1行呼び出せばよしなにやってくれます(getComponent()の実装は不要です)。
public void onCreate(Bundle savedInstanceState) {
ComponentHelper.createActivityComponent(this).inject(this);
super.onCreate(savedInstanceState);
}