LoginSignup
44
28

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-20

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

44
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
28