Li::Feel

りとるす的雑記帳。

Android開発:Dagger2でDI(Dependency Injection)する備忘録

DIってなんだ

Dependency Injectionの略で、依存性の注入と訳すらしいです。なんか依存性注入って訳すんじゃないよって記事を見た気がしますが、話がややこしいのでスルーします。 Dと呼ばれるモノは基本的にはライブラリがなくても、外からモジュール等を注入できる構造になっていればいいようですが、プログラムの構造に影響を与えたり、AndroidのActivityやFragmentなどの癖の強いオブジェクトの類だとしんどそうな印象です。

Daggerは、DIコンテナと呼ばれるDIのお助けツールで、@OverrideなどでおなじみのJavaアノテーションと一部のクラスの追加でお手軽にDIができてしまうという代物です。 google.github.io

DIコンテナは、リフレクションに置き換えて実行時にリフレクションしまくるという方法で実装されているものがありますが、リフレクションなので基本的に負荷が高めのようです。 Daggerでは、ビルド時に一部のコードを自動生成することによってDIを実現しているので、リフレクションを使ったDIよりも負荷が低く、高速にDIができるようです。 特に、Dagger2はGoogle謹製なので、Androidでは重宝されているようです。 その他、Dagger以外にはSpring FrameworkとかDIコンテナらしいですね(よく知らない)。

DI、一体何がうれしいのか

システム設計の評価基準で、モジュール結合度というのがあるのはIPAの春と秋にやる試験で出てくるので学生でも聞いたことがあるという人は多いはず。 簡単に言えば、DIを使うことでモジュール結合度が下がり、単品でテストがしやすくなるって思っておけばたぶん大丈夫なのでは。 後述のprovideで提供するインスタンスをすっと差し替えてあげれば、あっさり単体テストができるようになるわけです。 個人的に、AndroidにおけるContextとかは神インスタンスという感じの割には、別にプログラマーが生で触れることってあまりないので、外から注入できるのは非常にありがたい気がします。

早速使う

Dagger2でのDIは全部で3種類Injectionの方法がある。

この記事では、Constructor Injectionの方法と、Field Injectionの方法だけメモっておくことにします。

DaggerでDIするための準備

注入内容の用意

Daggerでは、注入内容をModuleというクラスで用意します。 このModule内のprovideHogeメソッドでインスタンスを生成し、依存性があるインスタンスに注入します。 ここでは、ちょっとググってもあまり日本語の情報がない2種類の書き方を紹介します。

@Module
public class InfraModule {

    @Provides
    @Singleton
    public AuthInfoRepository provideAuthInfoRepository(Application application){
        return new AuthInfoRepositoryImpl(application);
    }

    @Provides
    @Singleton
    public ApiClient provideApiClient(){
        ApiClient cl= ApiClientFactory.getSingleton();
        cl.setOAuth(BuildConfig.CONSUMER_KEY, BuildConfig.CONSUMER_SECRET_KEY);
        return cl;
    }

}

基本的には、@Moduleを付けることで、こいつがModuleであることをDaggerに明示します。

また、インスタンスを提供するメソッドは、provideAuthInfoRepositoryや、provideApiClientといったような名前で命名する規則があるほか、@Providesアノテーションを付ける必要があります。 Daggerの機能で、@Providesと一緒に@Singletonを付けておくと、シングルトンインスタンスになるため、Singletonパターンの実装をサボれたりします。

注入する依存性が更に依存性を持つような場合、メソッドの引数に他のモジュールでprovideされているものであれば、勝手にDaggerが依存性を注入して良きようにしてくれます。

その他、型さえ合っていれば中の処理は気にしないので、外側に提供するインターフェースと、実装をわけるといったことももちろんOKです。

こんなこともあり、ModuleクラスはFactoryMethodパターンの集合に近い実装になるかと思います。

AndroidのApplicationインスタンスを依存性として提供するなら、こんな感じの実装に。

@Module
public class AppModule {
    private Application app;

    public AppModule(Application app){this.app=app;}

    @Provides
    @Singleton
    public Application provideApplication(){return app;}

}

Componentの用意

Daggerでは、Moduleと実際の注入先の橋渡しとして、Componentを用意する必要があります。

@Singleton
@Component(
        modules={
                AppModule.class,
                InfraModule.class
        }
)
public interface AppComponent{
        void inject(LoginActivity activity);
}

このComponentでは、@Componentアノテーションでモジュールを指定し、HogeComponentという名前のインターフェースにinjectメソッドを定義するだけでOKです。 injectメソッドの引数には、依存性の注入先のクラスを指定します。 その他、依存性を注入したいクラスが増えたらオーバーロードすればOKです。

Applicationクラスに細工する

Componentインターフェースなどをそのままにしても何も起こらないので、Androidで一番上位のライフサイクルを持つApplicationインスタンスを継承して、独自のApplicationを作ります。

public class MyApp extends Application {

    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        // AppComponentでdeprecatedエラーが出るとき
        // そのモジュールが使用されていないため、no-opになる。無視可
        appComponent=DaggerAppComponent
                .builder()
                .appModule(new AppModule(this))
                .infraModule(new InfraModule())
                .build();
    }

    public AppComponent getAppComponent(){
        return appComponent;
    }
}

このApplicationでは、AppComponentのインスタンスを持つようにします。DaggerAppComponentはビルドすると自動的に作成されますので、builder()を用いてインスタンス化したモジュールを指定し、最後にbuild()すれば準備はOKです。後でComponentを使って依存性をActivityに注入をするので、Componentを取り出せるようにアクセサメソッドを用意しておきます。

ちなみに、自作のApplicationを使用する場合は、AndroidManifest.xmlのapplicationタグのandroid:name属性をいじる必要があります。

    <application
        android:name=".MyApp"
        ....

ここまでがDIコンテナの依存性注入のための準備になります。 ここから先は、実際に依存性を注入する方法について見ていきます。

Daggerで実際にDIする

Field Injection

Field Injectionをする場合は、注入先のフィールドに対して、@Injectアノテーションをつけ、Applicationインスタンスが保持しているAppComponentのinjectメソッドを呼び出すことによって、フィールドに対して依存性注入を行います。 AndroidのActivityなど、コンストラクタにアクセスできないものについては、Field Injectionを用いてDIするのが推奨されているようです。

public class LoginActivity extends AppCompatActivity {

    @Inject
    ApiClient apiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        ((MyApp)getApplication()).getAppComponent().inject(this);
    }
}

Constructor Injection

Constructor Injectionをする場合は、コンストラクタに@Injectアノテーションをつけ、依存するクラス型の変数を引数にとることで、依存性の注入を行うことができます。 コンストラクタ内で注入した内容は、フィールドに保存することによってインスタンス内で自由に使えるようになるわけですね。

public class LoginActivityUtil {
    private LoginActivity activity;
    private ApiClient apiClient;

    @Inject
    public LoginActivityUtil(ApiClient apiClient) {
        this.apiClient=apiClient;
    }
    …
}

Constructor Injectionの場合は、Componentインターフェースへのinjectメソッドの追加は不要のようです。

DIの使いどころは?

Androidは、ActivityやらFragmentがなんでもできすぎるせいで、Fatになりやすい傾向があるので、DIコンテナを使ってしっかりと層分けをすると、かなり見通しがよくなる気がします。 Android-CleanArchitectureはDaggerを使って実現されていますが、ちょっとそのままだと冗長で、言うほど大きくないアプリを作るときは必要なモノだけ抜粋したものをつかうのが良いんじゃないでしょうか。 ちょうどこのサイト掲載のやつとかはサイズ感が良い感じで読みやすいです。 tech.recruit-mp.co.jp

さいごに

備忘録で自分の分かったことをノート代わりに書いているだけなので、間違ってたら教えて欲しいです。 Daggerにはその他にも便利機能が山ほどあるので、少しずつ勉強していきたい次第です。 あとはてなブログMarkdownで書けるのはやっぱり最高ですね。

References

Dagger2を味見してみたkazucocoa.wordpress.com