Kotlinで DI （Dependency Injection）~ Dagger 編

Androidアプリに[Dagger2](https://github.com/google/dagger)を使ってDIしてみます。
DIがどんなものかというのは[こちら](https://qiita.com/sudachi808/items/364add4e96a8d6edc82b)。

Daggerの他にもDIフレームワークがあります。
[Koin](https://insert-koin.io/)を使ったDIは[こちら](https://qiita.com/sudachi808/items/8e03503f52b4f11533a2)。

## やりたいこと

`ViewModel`に依存する`Activity`があって...

```
+-----------+
| Activity  |
+-----+-----+
      |
      ▽
+-----------+
| ViewModel |
+-----------+
```

`ViewModel`からデータを貰うようなアプリを作ります。

```
+-----------+    +-----------+
| Activity  |    | ViewModel |
+-----+-----+    +-----+-----+
      |                |
      |     greet()    |
      +--------------->|
      |<---------------+
      |      Hello     |
```

`ViewModel`を`Activity`に注入するようにします。


## プロジェクトへDagger2を導入
まずはこれがないと始まりませんので。

```groovy:app/build.gradle
apply plugin: 'kotlin-kapt'

// (省略)

dependencies {

    // (省略)

    implementation      "com.google.dagger:dagger:2.14.1"
    annotationProcessor "com.google.dagger:dagger-compiler:2.14.1"
    kapt                "com.google.dagger:dagger-compiler:2.14.1"
    kaptTest            "com.google.dagger:dagger-compiler:2.14.1"
}
```

## 実装
### ViewModel
```kotlin
class MainViewModel {
    fun greet(): String {
        return "こんにちわ"
    }
}
```
### モジュール
DaggerのDIパターンに必要らしいです。
`ViewModel` のインスタンスを吐き出すマシーンのようなものです（私の解釈）。

```kotlin
@Module
class ViewModelModule {
    @Provides
    fun provideMainViewModel(): MainViewModel {
        return MainViewModel()
    }
}
```
### コンポーネント
これもDaggerのDIパターンに必要らしいです。
モジュールの吐き出すインスタンスを何処に注入するのか指定するものです（私の解釈）。

```kotlin
@Component(modules = [ViewModelModule::class])
interface MainActivityComponent {
    fun inject(activity: MainActivity)
}
```
私の感覚だと「逆じゃね？」と思います。
注入される側が、どのインスタンスを欲しているのか指定する方が自然では？
まぁ、同じことですが...

### いったんビルド
モジュールとコンポーネントを書いたら、いったんプロジェクトをビルドします。
そうしておかないと、このあとアクティビティで使う予定のクラスが見つからなくて困ります。
アノテーションプロセシングとやらでコードを自動生成しているようです。

### アクティビティ
`ViewModel`を代入するプロパティを`lateinit`で用意しておいて、`@Inject`を付けておきます。

メンバーに`component`というプロパティを定義して、そこにコンポーネントを持っておきます。
また、`viewModelModule`メソッドの引数に`ViewModelModule`のインスタンスを指定して、コンポーネントにモジュールをロードしておきます。

`inject`メソッドが呼ばれると、`@Inject`の付いたプロパティにインスタンスが注入されます。
Activityにはコンストラクタインジェクションができないのでこうするようです。

```kotlin
class MainActivity : AppCompatActivity() {

    @Inject lateinit var viewModel: MainViewModel

    private val component = DaggerMainActivityComponent.builder()
            .viewModelModule(ViewModelModule())  // <-- これ
            .build()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        this.component.inject(this)  // <-- これ

        println("_/_/ ${this.viewModel.greet()}")
    }
}
```

ここまでできたら、アプリケーションを実行してみます。

### 結果
こんな感じでログが表示されました。
ちゃんとDIできてるようです。

```text:logcat
06-25 00:59:09.107 3575-3575/com.example.mydaggerapp I/System.out: _/_/ こんにちわ
```

## 依存関係を追加するとどうなるか

`ViewModel`が`Repository`に依存するようにして、その `Repository`を`ViewModel`に注入してみようと思います。

こんな感じです。

```
+-----------+    +-----------+    +-------------+
| Activity  |    | ViewModel |    | Repository  |
+-----+-----+    +-----+-----+    +------+------+
      |                |                 |
      |     greet()    |                 |
      +--------------->|     greet()     |
      |                +---------------->|
      |                |<----------------+
      |<---------------+     Hello       |
      |     Hello      |                 |
```
### Repository
```kotlin
class GreetingRepository {
    fun greet(): String {
        return "こんにちわ from Repository"
    }
}
```
### ViewModel
引数に`GreetingRepository`を受け取るコンストラクタを定義して`@Inject`を付けます。

```kotlin
class MainViewModel @Inject constructor(private val greetingRepository: GreetingRepository) {
    fun greet(): String {
        return this.greetingRepository.greet()
    }
}
```

### モジュール
新たに`GreetingRepository`クラスを作ったので、モジュールも追加します。

```kotlin
@Module
class RepositoryModule {
    @Provides
    fun provideGreetingRepository(): GreetingRepository {
        return GreetingRepository()
    }
}
```
また、`ViewModelModule`クラスの`provideMainViewModel`メソッドの引数に`GreetingRepository`を取るようにします。
こうしておくとDaggerによってインスタンスが自動的に注入されます。

```kotlin
@Module
class ViewModelModule {
    @Provides
    fun provideMainViewModel(greetingRepository: GreetingRepository): MainViewModel {
        return MainViewModel(greetingRepository)
    }
}
```
### コンポーネント
`modules`に`RepositoryModule`クラスを追加します。

```kotlin
@Component(modules = [ViewModelModule::class, RepositoryModule::class])
interface MainActivityComponent {
    fun inject(activity: MainActivity)
}
```
ここまで書いたら例によって一旦ビルドします。

### アクティビティ
`RepositoryModule`のインスタンスをロードするように追記します。

```kotlin
class MainActivity : AppCompatActivity() {

    @Inject lateinit var viewModel: MainViewModel

    private val component = DaggerMainActivityComponent.builder()
            .repositoryModule(RepositoryModule())  // <--これ
            .viewModelModule(ViewModelModule())
            .build()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        this.component.inject(this)

        println("_/_/ ${this.viewModel.greet()}")
    }
}
```
### 結果
こんな感じでログが表示されました。
Repositoryからの値がちゃんとActivityまで渡ってきています。

```text:logcat
06-25 01:52:25.556 5928-5928/com.example.mydaggerapp I/System.out: _/_/ こんにちわ from Repository
```

## もっと実践的に
「モックを注入できるように」とか「Activityごとにいちいちコンポーネント作りたくない」とかあるのと思うので改良してみます。

### Repository
インタフェースを定義して、ユニットテストとかでモックへの差し替えを容易にします。

```kotlin
interface GreetingRepositoryContract {
    fun greet(): String
}

class GreetingRepository : GreetingRepositoryContract {
    override fun greet(): String {
        return "こんにちわ from Repository"
    }
}
```
### ViewModel
注入されるRepositoryの型もインタフェースにします。

```kotlin
interface MainViewModelContract {
    fun greet(): String
}

class MainViewModel @Inject constructor(private val greetingRepository: GreetingRepositoryContract) : MainViewModelContract {
    override fun greet(): String {
        return this.greetingRepository.greet()
    }
}
```

### モジュール
これもRepositoryの型をインタフェースにします。

```kotlin
@Module
class RepositoryModule {
    @Provides
    fun provideGreetingRepository(): GreetingRepositoryContract {
        return GreetingRepository()
    }
}

@Module
class ViewModelModule {
    @Provides
    fun provideMainViewModel(greetingRepository: GreetingRepositoryContract): MainViewModel {
        return MainViewModel(greetingRepository)
    }
}
```
### コンポーネント
アプリ全体で一つのコンポーネントを使うようにしてみます。
前項までで使っていた`MainActivityComponent`は不要なので削除します。

```kotlin
@Component(modules = [ViewModelModule::class, RepositoryModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}
```

### アプリケーションクラス
どこからでも参照できるように、コンポーネントをcompanion objectとして定義します。

```kotlin
class Application : android.app.Application() {

    companion object {
        lateinit var component: AppComponent private set
    }
}
```
ここまで書けたら、また例によって一旦ビルドします。

### アプリケーションクラス
今までViewModelに書いてたやつをApplicationの`onCreate`に書きます。
これでどこからでもコンポーネントを参照できます。

```kotlin
class Application : android.app.Application() {

    companion object {
        lateinit var component: AppComponent private set
    }

    override fun onCreate() {
        super.onCreate()

        // ↓↓これ
        component = DaggerAppComponent.builder()
                .viewModelModule(ViewModelModule())
                .repositoryModule(RepositoryModule())
                .build()
        // ↑↑これ
    }
}
```
### マニフェスト修正
アプリ起動時のクラスが`Application`になるように`android:name`属性を追加します。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mydaggerapp">

    <application
        android:name=".Application"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```
### アクティビティ
アプリケーションクラスの`component`から`inject`を呼び出して`ViewModel`を注入します。

```kotlin
class MainActivity : AppCompatActivity() {

    @Inject lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        Application.component.inject(this)  // <-- これ

        println("_/_/ ${this.viewModel.greet()}")
    }
}
```
### 結果
変わり映えしませんが、こんな感じでログが表示されました。
意図した通りに動いているようです。

```text:logcat
06-25 02:36:40.248 7143-7143/com.example.mydaggerapp I/System.out: _/_/ こんにちわ from Repository
```

## おわりに

と、まぁ、ひととおり試してみましたが...
ちょっと難解な感じがします。
特にコンポーネント。

つーか使いやすいか？これ？？

ブログとかQiitaで紹介してくれている人たちの解説も分かり難い例ばかりで、
なぜもっと簡単に説明してくれないのだろうか？

>If you can’t explain something in simple terms, you don’t understand it
>"簡単な言葉で説明できないならあなたは理解していない"
>
> リチャード・P・ファインマン

とりあえずは以上です。