Edited at

Dagger 2でAndroidのライフサイクルごとのSingletonを実現する

More than 3 years have passed since last update.


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を定義したりしていたのですが、他のプロジェクトでも同じことやると考えるとめんどくさくなったのでライブラリに切り出しました。

https://github.com/ypresto/scabbard

最初のセットアップを終わればあとはonCreate()で下記のように1行呼び出せばよしなにやってくれます(getComponent()の実装は不要です)。

public void onCreate(Bundle savedInstanceState) {

ComponentHelper.createActivityComponent(this).inject(this);
super.onCreate(savedInstanceState);
}