Edited at

Dagger 2.10 の Android 拡張を試してみる

More than 1 year has passed since last update.

この記事では Dagger 2.10 RC4 を使っています。


短い概要


  1. Dagger 2.10 で Android のための拡張モジュールが公開されました

  2. 拡張モジュールを使うとコンポーネントの取り回しがシンプルになります



    • BaseActivityBaseFragment を使わずに済みます

    • シンプルにはなりますが、準備の手間はかなり増えます



  3. とりあえず動作するところまで試したため、ベストプラクティスかは不明です


はじめに

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 アノテーションが付いている mActivityInjectorDispatchingAndroidInjector<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 でなければ) アラートダイアログが表示されるように書いているので、依存の解決がうまくできていればアラートダイアログが表示されます。

yeah.png


再び App.java

全ての準備が整ったら、一度ビルドをしてみます。 App.java 内では Dagger 2 が生成する DaggerAppComponent の解決が必要になるため、ビルドが成功していれば App.java を開いてみるとインポートが促されると思います。

image

インポートをして完成です。


最後に

Dagger 2.10 の Android 用の拡張モジュールを試してみました。正直なところ、まだ理解ができていないところが多く、この形で本当に良いのかという自信がありません。また、依存の注入先が 1 つ増えるごとに、対応する Module / Component を用意し、 AppComponent の @Component アノテーション内にモジュールをリストアップしなければならないのかと思うと、面倒くささが強く感じられてしまいます。ただ、依存関係を注入先ごとに定義でき、決まったルールで管理ができるメリットもあると思うので、必要に応じて拡張モジュールを使っていきたいと思いました。