Android
DI
Dagger2

Dagger2.1xの導入サンプル

きっかけ

  • UIテストを書くときに、プロダクトのコードを変更せずにスタブを使ってテストしたい。

  • Daggerでオブジェクトの注入をして実現する。

  • 導入して綺麗に書けたと思うのでDagger2を導入したサンプルコード記述します。(UIテストへのDaggerの導入については別の記事に投稿する予定です)

環境

  • Android Studio3.0.1
  • dagger2
  • Java (KotlinのDIはDagger以外もあって悩ましそう)

参考にした記事

  • New Android Injector with Dagger 2 — part 1
  • 上や他の記事をみながら、サンプルアプリを動かして記事を見直して動きを把握しました。アノテーションは動きがわかりづらいです。役割や動きを把握までけっこう時間かかりました。:laughing:

サンプルアプリ

導入の進め方

いくつかクラスが必要ですので1つずつ作成していきます。動きを確認できる最低限のコードを用意します。

app/build.gradleに設定を記述する

dependencies {
    def daggerVersion = "2.14.1"
    compile "com.google.dagger:dagger-android:$daggerVersion"
    compile "com.google.dagger:dagger-android-support:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
}

dagger関連のクラスをbuild.gradleに記述します。公式にはdagger-compilerの記述がなかったのですが
こちらも必要でした。

カスタムApplicationクラスの作成

public class MainApplication extends Application implements HasActivityInjector, HasFragmentInjector {

    @Inject
    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    @Inject
    DispatchingAndroidInjector<Fragment> dispatchingFragmentInjector;


    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }

    @Override
    public DispatchingAndroidInjector<Fragment> fragmentInjector() {
        return dispatchingFragmentInjector;
    }
}

クラスを作成した段階ではDaggerAppComponentを参照できないですが、後から解決されるのでこのまま進めます。
(HasFragmentInjectorはこのサンプルではFragment使っていないので必要なさそうです。)

AppComponentの記述

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class})
public interface AppComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }

    void inject(MainApplication application);
}

DaggerAppComponentの雛形になるクラス

AppModuleの記述

@Module
class AppModule {

    @Singleton
    @Provides
    Context provideContext(Application application) {
        return application.getApplicationContext();
    }
}

アプリ全体で使いまわしたいオブジェクトを提供するようにします。
今回はContextが対象となりますが、APIクライアントのrepositoryなどを記述していくと思います。

ActivityBuilderの記述

@Module
public abstract class ActivityBuilder {
    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity bindmainActivity();
}

オブジェクトをInjectするActivityをここで記述します。

MainActivityModuleの記述

@Module
public class MainActivityModule {
//    @Provides
//    HogeRepository provideRepository(MainActivity mainActivity) {
//        return new HogeRepository(mainActivity);
//    }
}

このサンプルではAppModuleに記述したContextを利用するだけなので、ここは何も記述していません。対象のActivityでInjectが必要ならコメントアウトの記述を参考にしてください。

MainActivityの記述

public class MainActivity extends AppCompatActivity {

    @Inject
    Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(context, "message", Toast.LENGTH_LONG).show();
    }
}

オブジェクトをInjectするクラスです。Contextを利用してToastを表示しています。
Toastが表示されるでしょうか。

終わりに

Daggerを使ったDIを試しました。以前のDaggerと比べてActivityに記載するコードが少なくスッキリしています。
アプリ設計でDIを使ってレイヤー間を疎結合にできるので、これから使っていきたいと思います。