Dagger2を雰囲気で使っているので、AndroidアプリへのDagger2導入方法、ComponentやModuleの役割や関係などを自分用にまとめました。
この記事の対象者
- AndroidやKotlin、DIがある程度わかっている方
- AndroidアプリにDagger2を導入する手順を知りたい方
- Dagger2初心者
この記事でやること
- Kotlinで記述したAndroidアプリへDagger2を導入します。
Gradle設定ファイルへの追加
まず最初にAndroid Studioでappモジュールのbuild.gradleを開き、ライブラリを追加します。
Dagger2のバージョンは、特に理由が無い限りそのときの最新で良いかと思います。
最新バージョンの確認は公式サイトのリリースページを参照しましょう。
StackOverflowによるとKotlinで記述する場合はJavaの場合とbuild.gradleの記述が異なりますので気をつけましょう。
// kaptを追加する
apply plugin: 'kotlin-kapt'
dependencies {
// その他諸々の設定
// Dagger2
def dagger_version = "2.22.1"
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
ModuleとComponent
Dagger2ではModuleとComponentという2つのアノテーションをつけたクラスが出てきます。一見何がどのような役割なのか名前から予想がしづらいと思うので簡単に説明します。
- Module
何を注入するか指定します。注入するインスタンスをここで生成することもできます。 - Component
メンバー注入メソッドを定義し、どこに注入するかを指定します。
アプリ全体で使うインスタンスを注入する
最初に、DBへのアクセスやサーバへのアクセス、何かしらの設定などアプリ全体で使うインスタンスを注入する設定をしましょう。
何を注入するかはModuleに定義するので、Moduleを作成します。
以下のようにクラスに@Moduleアノテーションを追加します。
インスタンスを提供するメソッドに@Providesアノテーションを設定します。
メソッド名はprovide何たらとつける決まりです。
各種インスタンスの生成や初期化でContextが必要なことが多いので、ApplicationクラスのインスタンスをDIで引数として受け取っています。今回はApplicationクラスを継承したMyApplicationクラスを作成しています。(詳細は後述)
@Singletonアノテーションを設定すると、アプリ全体(正確にはComponentのスコープ)でシングルトンとなります。
生成にコストがかかるインスタンスはシングルトンにしておきましょう。
@Module
class MyApplicationModule {
// アプリ全体で注入したいインスタンスを設定する。
@Singleton
@Provides fun provideHoge(application: MyApplication) = "Hoge"
}
次にこのModuleを使うComponentを作成します。
Componentはinterfaceやabstract classに@Componentアノテーションを設定します。
modules要素として、そのComponentで注入したいインスタンスが定義されているModuleを列挙します。
@Component.Builderアノテーションを設定したBuilderインターフェイスを追加しています。
これはMyApplicationComponentインスタンスを生成する際に使用します。
@BindsInstanceアノテーションを設定したMyApplicationクラスのセッターを追加します。ここでMyApplicationインスタンスを設定することで、MyApplicationModuleでMyApplicationインスタンスが注入できるようになります。
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class, // よりシンプルにDIできる公式の便利Module(必須)
MyApplicationModule::class,
ActivityModule::class // 各Activityの設定(後述)
])
interface MyApplicationComponent {
@Component.Builder
interface Builder {
// MyApplicationModuleでMyApplicationのインスタンスが利用できるようbindする
@BindsInstance
fun application(application: MyApplication): Builder
// 設定済みのMyApplicationComponentインスタンスを生成する
fun build(): MyApplicationComponent
}
// メンバー注入メソッド、MyApplicationへ注入する際に使用する
fun inject(app: MyApplication)
}
さて、これでModuleとComponentができました。次にMyApplicationを作成します。
Android SupportでActivityへの注入を簡単にするため、HasActivityInjectorインターフェイスを実装します。
onCreateメソッドをオーバーライドし、MyApplicationインスタンスに対して注入します。
注入するには先ほど作成したMyApplicationComponentを使います。
MyApplicationComponent.builder()で、先ほど作成したBuilderのインスタンスを取得でき、MyApplicationインスタンスを設定し、ビルドします。そしてこれまた先ほど作成したメンバー注入メソッドのinjectメソッドを使い、MyApplicationインスタンスへ注入します。
するとdispatchingAndroidInjectorが使えるようになります。
// いつも通りApplicationクラスを継承する
class MyApplication : Application() , HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
// HasActivityInjectorインターフェイスで定義されているメソッドを実装する
// 注入されたdispatchingAndroidInjectorを返す
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
override fun onCreate() {
super.onCreate()
DaggerMyApplicationComponent
.builder()
.application(this)
.build()
.inject(this)
}
}
MyApplicationクラスを作成したら、忘れずAndroidManifest.xmlにそれを使う設定をしましょう。
<application
android:name=".MyApplication"
以下略
次に、ActivityへMyApplicationModuleで生成したHoge文字列を注入してみましょう。
ActivityModuleクラスをabstractで作成し、Activity毎にcontributeから始まるメソッドを作成します。今回はMainActivityとSubActivityを作ります。
そして、それぞれのメソッドに@ContributesAndroidInjectorアノテーションを設定します。
modules要素に、それぞれの画面毎に使用するModuleを設定します。それぞれのModuleは後述します。
@ContributesAndroidInjectorはSubcomponentを作成しModuleを設定してくれる便利アノテーションです。
画面毎にSubcomponentが分かれるため、アプリ全体で使うインスタンスと、画面毎に使うインスタンスを分けて定義することができます。また、スコープも分かれるため、画面毎のSingletonも可能です。
@Module
abstract class ActivityModule {
// MainActivityに注入する際に使用するModuleを指定する。
// 自動的にSubcomponentが生成され、そのSubcomponentにModuleが設定される。
@ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun contributeMainActivityInjector(): MainActivity
@ContributesAndroidInjector(modules = [SubActivityModule::class])
internal abstract fun contributeSubActivityInjector(): SubActivity
}
画面毎のModuleも作成しましょう。
このDataはMainActivityModuleで設定したため、MainActivityからのみ使えることになります。
@Module
class MainActivityModule {
@Provides fun provideData() = Data(System.currentTimeMillis())
}
data class Data(var id: Long)
では実際にMainActivityで使ってみましょう。
注入して欲しい変数は@Injectアノテーションを設定します。
DaggerAppCompatActivityクラスを継承すると、onCreateの呼び出し時点ですでに注入された状態となっています。
DaggerAppCompatActivityクラスを継承しない場合はonCreateの最初で次のメソッドを実行すると注入が実行されます。
AndroidInjection.inject(this);
class MainActivity : DaggerAppCompatActivity() {
@Inject lateinit var hoge: String
@Inject lateinit var data: Data
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("Test", "hoge=$hoge data=$data")
}
}
Logcatに次のようなログが出ていれば成功です!
最後に
Dagger2はバージョンアップで便利な書き方ができるなどして記事によって微妙に書き方が異なっているようで混乱します。
公式ページに書かれているコードも、一部が省略されているのかそのままでは動きません。
導入方法をまとめてみましたが、どれが最新のバージョンで最適な方法なのかいまいち把握できませんでした。
まだ私もDagger初心者を脱出できていないので、コメントでもっとこうした方が良いなどありましたらお願いいたします。