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

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

## 短い概要

1. Dagger 2.10 で Android のための拡張モジュールが公開されました
2. 拡張モジュールを使うとコンポーネントの取り回しがシンプルになります
    * `BaseActivity` や `BaseFragment` を使わずに済みます
    * シンプルにはなりますが、準備の手間はかなり増えます
3. とりあえず動作するところまで試したため、ベストプラクティスかは不明です

## はじめに

[Dagger](https://github.com/square/dagger/) は Square が開発した DI ライブラリですが、 [Dagger 2](https://github.com/google/dagger) は Google がフォークしたバージョンです。公式に Dagger は Deprecated になっており、 Dagger 2 への移行ガイドが Google から公開されています。 [DroidKaigi カンファレンスアプリ](https://github.com/DroidKaigi/conference-app-2017)でも Dagger 2 は使われており、セッションでも Dagger 2 に関するものがあるなど、 Android アプリの開発現場で多く使われています。

そんな Dagger 2 ですが、 Dagger 2.10 RC から Android 用の拡張モジュールが公開されました。 [Dagger 2 の GitHub Pages にはしれっと拡張モジュールを用いた Android での利用方法が書いてある](https://google.github.io/dagger/android.html)のですが、何とも不十分で分かりづらいと思いました。

今回はこの Android 用の拡張モジュールを試してみたいと思います。

## 準備

### build.gradle

はじめに `app/build.gradle` に Dagger 2 の依存関係を書きます。

```groovy
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 のみ実装します。

```java
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 を注入する必要があるので、解決できるように実装します。

```java
@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 に依存を注入できるようにします。

```java
@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 にどのような依存があるかを示します。

```java
@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 に注入したい依存の実体を書いていくことになります。

```java
@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 にたどり着きました。

```java
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](https://qiita-image-store.s3.amazonaws.com/0/5036/a237b491-5ef3-5809-26a3-dc4b742e77cb.png)

### 再び App.java

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

![image](https://qiita-image-store.s3.amazonaws.com/0/5036/964bec6b-3cfb-9511-aec7-e54d8bfcace7.png)

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

## 最後に

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