この記事では Dagger 2.10 RC4 を使っています。
短い概要
- Dagger 2.10 で Android のための拡張モジュールが公開されました
- 拡張モジュールを使うとコンポーネントの取り回しがシンプルになります
-
BaseActivity
やBaseFragment
を使わずに済みます - シンプルにはなりますが、準備の手間はかなり増えます
-
- とりあえず動作するところまで試したため、ベストプラクティスかは不明です
はじめに
Dagger は Square が開発した DI ライブラリですが、 Dagger 2 は Google がフォークしたバージョンです。公式に Dagger は Deprecated になっており、 Dagger 2 への移行ガイドが Google から公開されています。 DroidKaigi カンファレンスアプリでも Dagger 2 は使われており、セッションでも Dagger 2 に関するものがあるなど、 Android アプリの開発現場で多く使われています。
そんな Dagger 2 ですが、 Dagger 2.10 RC から Android 用の拡張モジュールが公開されました。 Dagger 2 の GitHub Pages にはしれっと拡張モジュールを用いた Android での利用方法が書いてあるのですが、何とも不十分で分かりづらいと思いました。
今回はこの Android 用の拡張モジュールを試してみたいと思います。
準備
build.gradle
はじめに app/build.gradle
に Dagger 2 の依存関係を書きます。
ext {
dagger_version = '2.10-rc4'
}
dependencies {
compile "com.google.dagger:dagger:${dagger_version}"
compile "com.google.dagger:dagger-android:${dagger_version}"
compile "com.google.dagger:dagger-android-support:${dagger_version}"
annotationProcessor "com.google.dagger:dagger-compiler:${dagger_version}"
annotationProcessor "com.google.dagger:dagger-android-processor:${dagger_version}"
}
dagger-android
dagger-android-support
dagger-android-processor
これらが Android 用の拡張モジュールです。 Android Support Library の Fragment を用いる場合には dagger-android-support
が必要になります (ほとんどの場合で必要だと思います) 。
App.java
次に Application クラスを継承したカスタム Application クラスを用意します。ここでは App という名前にします。このカスタム Application クラスには、 Dagger の Android 用の拡張モジュールに用意されている HasDispatchingActivityInjector を実装します。これは Activity に依存を注入するために必要なものですが、 Android Support Library の Fragment に依存を注入したい場合は、 HasDispatchingSupportFragmentInjector も合わせて実装する必要があります。
ここでは HasDispatchingActivityInjector のみ実装します。
public class App extends Application implements HasDispatchingActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> mActivityInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.create().inject(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return mActivityInjector;
}
}
App#onCreate
内で DaggerAppComponent.create().inject(this);
を呼び出していますが、これは後ほど用意する AppComponent の実装です。 Dagger 2 がコンパイル時にコード生成します。インジェクトメソッドが呼ばれることで、 @Inject
アノテーションが付いている mActivityInjector
に DispatchingAndroidInjector<Activity>
のインスタンスが注入されます。
AppCompnent.java
AppComponent.java を用意します。先の App.java に DispatchingAndroidInjector を注入する必要があるので、解決できるように実装します。
@Singleton
@Component(modules = {
AndroidInjectionModule.class,
MainActivityModule.class
})
public interface AppComponent {
void inject(App app);
}
AppComponent#inject(App)
を用意することで、 App.java 内で依存を注入できるようにします。また、 Dagger 2 の Android 用の拡張モジュールに用意されている AndroidInjectionModule を @Component
アノテーションの modules
に渡します。さらに、後ほど用意する MainActivityModule も渡します。
MainActivityModule.java
MainActivityModule.java を用意します。このモジュールでは Dagger 2 の AndroidInjector が MainActivity に依存を注入できるようにします。
@Module(subcomponents = {MainActivityComponent.class})
public abstract class MainActivityModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MainActivityComponent.Builder builder);
}
このあたりはまだ私も役割と挙動を理解できていません。さらに登場人物は増えます。 MainActivityComponent を用意します。
MainActivityComponent.java
MainActivityComponent.java を用意します。このコンポーネントでは MainActivity にどのような依存があるかを示します。
@Subcomponent(modules = ActivityModule.class)
public interface MainActivityComponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
public abstract void activityModule(ActivityModule activityModule);
@Override
public void seedInstance(MainActivity instance) {
activityModule(new ActivityModule(instance));
}
}
}
AndroidInjector インターフェースを継承します。型パラメータには依存の注入先である MainActivity を渡します。どのような依存があるかは @Subcomponent
アノテーションの modules
に書くことになります。ここでは ActivityModule を渡します。
さらに、 MainActivityComponent 内にこのコンポーネントのビルダークラスを abstract
で定義します。ビルダークラスには @Subcomponent.Builder
アノテーションを付けます。また、 AndroidInjector.Builder クラスを継承し、同様に型パラメータには MainActivity を渡します。
ActivityModule を引数に取る activityModule
抽象メソッドを用意していますが、これは ActivityInjector.Builder#seedInstance
内で ActivityModule に Activity のインスタンスを渡すためのものです。 seedInstance
メソッドを実装し、 activityModule
に必要な要素を渡してインスタンスを作るように定義づけします。
ActivityModule.java
ActivityModule.java を用意します。このモジュールでは MainActivity に注入したい依存の実体を書いていくことになります。
@Module
public class ActivityModule {
private final AppCompatActivity mActivity;
public ActivityModule(AppCompatActivity activity) {
mActivity = activity;
}
@Provides
public LayoutInflater provideLayoutInflater() {
return LayoutInflater.from(mActivity);
}
}
LayoutInflater を注入できるようにしてみました。ファクトリメソッドである LayoutInflater#from
には Context を渡す必要がありますが、アプリの Theme の解決には Activity Context が必要となります。そのため Activity のインスタンスが必要になりますが、これは MainActivityComponent.java 内で渡されてくるように定義済みです。
MainActivity.java
ようやく依存の注入先である MainActivity にたどり着きました。
public class MainActivity extends AppCompatActivity {
@Inject
LayoutInflater mLayoutInflater;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (mLayoutInflater != null) {
new AlertDialog.Builder(this)
.setMessage("Hello, world")
.setPositiveButton(android.R.string.ok, null)
.show();
}
}
}
ここで新しいのは AndroidInjection.inject(this);
です。これによって、 @Inject
アノテーションが付いている LayoutInflater mLayoutInflater;
にインスタンスが注入されます。このコードでは LayoutInflater が注入されていれば (null でなければ) アラートダイアログが表示されるように書いているので、依存の解決がうまくできていればアラートダイアログが表示されます。
再び App.java
全ての準備が整ったら、一度ビルドをしてみます。 App.java 内では Dagger 2 が生成する DaggerAppComponent の解決が必要になるため、ビルドが成功していれば App.java を開いてみるとインポートが促されると思います。
インポートをして完成です。
最後に
Dagger 2.10 の Android 用の拡張モジュールを試してみました。正直なところ、まだ理解ができていないところが多く、この形で本当に良いのかという自信がありません。また、依存の注入先が 1 つ増えるごとに、対応する Module / Component を用意し、 AppComponent の @Component
アノテーション内にモジュールをリストアップしなければならないのかと思うと、面倒くささが強く感じられてしまいます。ただ、依存関係を注入先ごとに定義でき、決まったルールで管理ができるメリットもあると思うので、必要に応じて拡張モジュールを使っていきたいと思いました。