Dagger2をいまさらだけど導入する2019

自分の担当するプロジェクトで全然Daggerに触れる機会がないので、いい機会なのでDagger2を今更導入してみました。

# きっかけ
自分が今取り組んでいるDDDにおいて、各レイヤーごとに処理を分ける必要がありますが、プレゼンテーション層から下のレイヤーのインスタンスを作るのが非常に手間だったからです。
DIを利用して依存性を注入してあげれば、ActivityやFragmentから

```kotlin:HomeActivity.kt
private lateinit var homeApplicationService: HomeApplicationService

override fun onCreate(bundle: Bundle?) {
    ・・・
    val homeDatabase = HomeDatabase(this)
    val homeCache = HomeCache(this)
    val homeDataStore = HomeDataStore(homeDatabase, homeCache)
    val homeRepository = HomeRepository(homeDataStore)
    homeApplicationService = HomeApplicationService(homeRepository)
    ・・・
```
みたいなことをしなくて済むからです。（現在のプロジェクトでは仕方なくこれをしてます）

# Dagger2
まずはbuild.gradleのdependenciesにライブラリを指定します。

```gradle:build.gradle
apply plugin: 'kotlin-kapt'

dependencies {
    ・・・
    // dagger
    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:2.24"
}
```

Javaの場合はkapt使えないので、annotationProcesserで指定してあげる必要があるので注意です。

# DIパターン
DIについては2パターンあると思ってます。
1. ActivityやFragmentのインスタンスをDIする。
2. 1.以外のモジュールのインスタンスをDIする。

1と2でやり方があるのですが、DIする場合は基本的にActivityやFragmentでインスタンスを作ることで、結合が疎になると思うので、まとめて覚えてしまうほうがいいかなと思います。（所感）

# とりあえず
この記事を読んでもいいですが、すでDaggerについての記事はたくさんあるので、
以下を参照すれば大丈夫です。
ぼくは以下を参考に実装しました。
https://qiita.com/satorufujiwara/items/f42b176404287690f1d0
https://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370
https://qiita.com/superman9387/items/bea94e4316c2ccf8fb68
https://qiita.com/kenji1203/items/0f221272620309ebc1e9

https://speakerdeck.com/kgmyshin/android-multi-module-with-dagger

https://github.com/android/architecture-components-samples

興味がある方は読み進めてみてください。

# AppComponentを作る
まずはAppComponentを作ります。

Componentは基本的にAppComponentくらいしか使ってないケースが多いですが、もしかしたらマルチモジュールでの開発の場合などでは、AppComponentではなく○○Componentみたいなものを作り、必要があった場合に必要な場所でDagger○○ComponentをDIするという方法もあるかもしれません。（多分）
では、実装例です。

## 実装例

```kotlin:AppComponent.kt
@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    MainModule::class
])
interface AppComponent: AndroidInjector<App> {
    @Component.Factory
    abstract class Factory : AndroidInjector.Factory<App>
}
```

## 説明
### インタフェースの作成
`AppComponent`は`Interface`で定義します。
定義したら、`@Component`のアノテーションを付与します。
`@Component`内に`module = []`を記述し、関連するModuleを指定してあげます。
ようするに、ここは各Moduleで設定しているインスタンスを作る窓口的な役割です。

ここでは
`AppModule`、`AndroidSupportInjectionModule::class`
の2つを`@Component`の`modules`に指定します。
`AppModule`については後述します。

### 継承元について
今回、`AndroidInjector<T>`を継承しています。
`T`にDIさせる場所を指定することでDIが出来ます。
今回は`App`としていて、独自アプリケーションクラスを指定していますが、`MainActivity`など、
Activityを指定することも可能です。
その代わり、指定したクラスでのインジェクトが必要になるので、DIする場所に気をつけてください。

### 抽象クラスFactoryの定義
インタフェース内に、`AndroidInjector.Factory`を継承したクラスを定義し、
`@Component.Factory`アノテーションを使用しています。
他の記事にもありますが、`@Component`には`Factory`や`Builder`があり、
そのクラスを定義してあげることでDIすることができるようになるので、
作成する必要があります。

`@Component.Builder`での実装例が今までの記事では書かれていますが、
`@Component.Builder`は非推奨になったので、今回は`Factory`を利用します。

### Appクラスを作る
この例では独自ApplicationクラスをInjectするクラスにしていしています。
ですので、Appクラスを作成します。

```kotlin
class App : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.factory().create(this)
    }
}
```

普通に`Application`クラスを継承する方法もありますが、今回は`DaggerApplication`を
継承元にします。
これにより、`inject`を行うメソッドをOverrideできますので、その中で生成された`DaggerAppComponent`を呼び出します。

生成したあとに`AndroidManifest.xml`に独自Applicationクラスを指定することを忘れないようにしてください。

```xml
    <application
        android:name=".App" 　// これ
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
```

### ビルドする
AppComponentクラスを作りますが、これだけでは動きません。
ビルドをして`DaggerAppComponent`を作りましょう。

Build -> Make Module　で作成できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151973/c349afe9-f234-31f7-d586-f32506988a34.png)

# Moduleを作る
AppComponentができたので、次はModuleを作りましょう。
まずサンプルとしてAppModule.ktというもの作ります。ここでは仮のRepositoryクラスをDIさせます。

```kotlin:HomeRepositoryImpl.kt
class HomeRepositoryImpl
    @Inject constructor(
        private val application: App
    ): HomeRepository {
    //    override fun get() = "daggerの確認だよ"
    @RequiresApi(Build.VERSION_CODES.N)
    override fun get(): String {
        return application.isDeviceProtectedStorage.toString()
    }
}
```

```kotlin:AppModule.kt
@Module
class AppModule {
    @Singleton
    @Provides
    fun providesHomeRepository(homeRepositoryImpl: HomeRepositoryImpl): HomeRepository
            = homeRepositoryImpl
}
```

今回は`@Provides`を使用しました。
基本的なお作法としてつけるメソッド名にはアノテーションに合わせるようです。（必須ではないハズ・・）

@Provides -> providesHomeRepository()
@Binds -> bindHomeRepository()
@ContributesAndroidInjector -> contributeHomeFragment()

指定する戻り値は、DIしたいインスタンスです。

## Bindパターン
Moduleは`@Bind`でも行えます。
`@Bind`の場合は以下です。これは`@Provides`しているのと同義です。

```kotlin
@Module
abstract class AppModule {
    @Singleton
    @Binds
    abstract fun bindHomeRepository(homeRepository: HomeRepository): HomeRepository
}
```
`@Binds`の方がすっきりしますね。
`@Binds`を利用する場合、同Module内に`@Provides`を指定することが出来ないので、お気をつけください。

## AppComponentに追加する
作成したModuleは、Componentに紐づけるかサブコンポーネントに紐付けるかしてあげないとDIされません。
先述していますが、AppComponentの@Component内にmoduleがあるので、そこで作成したModuleを紐付けてあげましょう。
今回のAppComponentはApp.ktでInjectしているので、アプリ全体で利用するためのInjectです。

```kotlin:AppComponent.kt
@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    MainModule::class,
    AppModule::class　// 追加する
])
interface AppComponent: AndroidInjector<App> {
    @Component.Factory
    abstract class Factory : AndroidInjector.Factory<App>
}
```

ここまでやったら、あとはAppModuleでインスタンスを作ったものをインジェクトするだけです！

```kotlin:MainActivity.kt
class MainActivity : DaggerAppCompatActivity() {

    @Inject
    lateinit var homeRepository: HomeRepository
}
```
これでOK！！！

# ActivityをDIする
次にActivityをComponentに指定していきます。
正直、この記事を書くまでは以下の疑問点がありました。

* ActivityをDIするってどういうこっちゃ
* ActivityはそもそもstartActivityで起動なのでは?
* とにかくよくわからん

とりあえず、参考にしていた記事を元に作ってみましょう。

## ActivityModuleを作る
先程作っていたアプリではMainActivityがAndroidManifestでLAUNCHER指定していたので、
基本的にApplication -> MainActivityと呼び出されるので、DIする必要がないように思えます。

なので、別のHomeActivityというものを作ってDIしてみたいと思います。

```kotlin:HomeActivity.kt
class HomeActivity: DaggerAppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        println("HomeActivity::onCreate")

        val fragmentTransaction = supportFragmentManager.beginTransaction()
        val fragment = HomeFragment()
        fragmentTransaction.add(fragment, "homefragment")
    }
}
```
こんなやつのModuleを作ります。

```kotlin:HomeActivityModule.kt
@Suppress("unused")
@Module
abstract class HomeActivityModule {
    @ContributesAndroidInjector
    abstract fun contributeHomeActivity(): HomeActivity
}
```

これをAppComponentに指定します。

```kotlin:AppComponent.kt
@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    MainModule::class,
    AppModule::class,
    HomeActivityModule::class // 追加する
])
interface AppComponent: AndroidInjector<App> {
    @Component.Factory
    abstract class Factory : AndroidInjector.Factory<App>
}
```

これでActivityをComponent経由でDIできました（？）

## 検証
もしApplicationクラスでDIされてしまっていたらHomeActivityは謎にonCreateされてしまっていることでしょう。
先程のHomeActivity::onCreate()にprintlnを仕込んだのでログを確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151973/94946db1-4e4d-f7ec-7b51-79bd2e2848ec.png)

**ログなし。**

よくわからんので調べてみました。
https://qiita.com/MoyuruAizawa/items/26cb093adbc778013467

これに書いてある情報だと、ActivityModuleはActivityのライフサイクルに合わせて関連しているものをDIしてくれるそうです。

結論としては、Activityに関連するものをDIするために、事前にAppComponentなどで予約しておくような感じかなと思いました。
この場合、Activity単体で何かをやるためにというわけではなく、それに付随するFragmentやViewModel、さらに下のレイヤーのインスタンスをSubComponentを利用してDIするのが有効です。

ということで、次でFragmentとそれに付随するViewModel、さらにその下のRepositoryまでDIするようにします。

# FragmentとViewModelをDIする
記事が長くなってきたのでだいぶ内容が荒くなってきていますが、最後にちゃちゃっとFragmentとViewModelをSubcomponentを用いてDIしたいと思います。

Subcomponentについては別で記事を書いて説明したいと思います。
イメージとしては
そのModuleに関連づくModuleを紐付けてDIしていきます。

では、Moduleです。

```kotlin:HomeModule.kt
@Module
abstract class HomeModule {
    @Binds
    @IntoMap
    @ViewModelKey(HomeViewModel::class)
    abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): HomeViewModel

    @ContributesAndroidInjector
    abstract fun contributeHomeFragment(): HomeFragment
}
```

ここではHomeViewModelとHomeFragmentをDIしています。
ViewModelの生成については
https://github.com/android/architecture-components-samples
や
https://qiita.com/satorufujiwara/items/f42b176404287690f1d0
を参考にするのが良いでしょう。

ViewModelを使ったことならわかると思いますが、
「コンストラクタによる値渡しをするには独自ViewModelProvider.Factoryを継承したクラスが必要」
です。
各ViewModelのコンストラクタの値をDIするためには、ViewModelProvider.Factoryを継承したクラスをDIする必要が出てくるわけです。

そこで、以下のようなViewModelFactoryクラスを用意します。

```kotlin:MyViewModelFactory.kt
class MyViewModelFactory
@Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
```

コンストラクタで渡されている`creators: Map<Class<out ViewModel>`は、
先程のHomeModuleのViewModelのアノテーション`@IntoMap`をつけたことでDIされているViewModelが入っています。

```kotlin
    @Binds
    @IntoMap ←これ
    @ViewModelKey(HomeViewModel::class)
    abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): HomeViewModel
```

そして、`@IntoMap`で入ったHomeViewModelのキーとして`@ViewModelKey`でClassを渡しています。
ちなみにこのViewModelKeyは先程の
https://github.com/android/architecture-components-samples
にもありますが、独自アノテーションクラスです。

```kotlin:ViewModelKey.kt
@MustBeDocumented
@Target (
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
```

これは僕は完全にコピペしました。
ようするに、ViewModelをDIするためにはViewModelFactory継承クラスが必要で、上述したようなViewModelFactoryクラスを作ってDIさせてあげる必要があります。

なので、AppModuleなどでBindｓもしくはProvidesしてあげましょう。

```kotlin:AppModule.kt
@Module
abstract class AppModule {
    @Binds
    abstract fun bindViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
```

利用する場合は以下のように利用します。

```kotlin:HomeFragment.kt
class HomeFragment : DaggerFragment() {
    @Inject
    private lateinit var homeViewModelFactory: MyViewModelFactory

    private val homeViewModel: HomeViewModel by viewModels {
        homeViewModelFactory
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.fragment_home, container, false)
        val textView: TextView = root.findViewById(R.id.text_home)
        homeViewModel.text.observe(this, Observer {
            textView.text = it
        })
        return root
    }
}
```

ここではViewModelをbyで呼び出す新しい方法を使ってます。
今まではどうしても`lateinit var`で定義して、onCreateで
`ViewModelProviders.of(this).get(XXX::class.java)`
のように書かないといけなかったので、非常に書きやすく便利になりました。

ということで、これでActivity→Fragment→ViewModelまでDIができるようになったはずです。

ViewModel以降のDIについては、各アーキテクチャに従うことになると思います。
僕のなんちゃってアジャイルでやる場合は、ViewModelさんは必要なドメインとなるApplicationServiceクラスをコンストラクタに保持することになるので、ViewModelのconstructorをinjectすることになります。

引数付きconstructorのDIの仕方を書きたいですが、この記事は少し長くなってしまったので、別途書きたいと思います。

# Dagger2を使ってみた感想
正直、DDDを使ってレイヤー化させていた僕としては、DIができるのは非常にやりやすかったです。魔法なのかと。
ただ、お作法があるので、それを守っていないとコンパイルエラーになるので、結構ハマることが多かったです。

# 最後に
みなさんも快適なDIライフをお送りください。
ここまで読んでくださった方ありがとうございます。ぜひ間違ってる部分などあると思うので、フィードバックいただけると幸いです。