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

  • 26
    いいね
  • 0
    コメント

この記事では 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 アノテーション内にモジュールをリストアップしなければならないのかと思うと、面倒くささが強く感じられてしまいます。ただ、依存関係を注入先ごとに定義でき、決まったルールで管理ができるメリットもあると思うので、必要に応じて拡張モジュールを使っていきたいと思いました。