ViewModelをDagger2でDIする

ViewModelをDagger2でDIする方法について書きます。
自分も理解するまでかなり苦労したので誰かの力になればと思います。

正直自分の完全に理解したわけではないので、ここが違うよ！！とかあれば優しく教えていただれば嬉しいです。😭

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ViewModelをDagger2を使ってDIする方法がまじでわからないので、一旦整理しよう。なにがわかっててなにがわからないのか、１からやり直すぞ</p>&mdash; shogo.yamada@Flutterが好き (@yshogo87) <a href="https://twitter.com/yshogo87/status/1054617315646484480?ref_src=twsrc%5Etfw">2018年10月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


ソースコードは下記に置いておきました。

https://github.com/yshogo/Dagger-Sample

## 準備

ライブラリを導入します。

```groovy
def daggerVersion = "2.16"
implementation "com.google.dagger:dagger-android:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
```


## 基本形

```kotlin

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MainViewModel
    private lateinit var binding: MainActivityBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ↓ここをDagger2を使ってDIしたい
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity)

        binding.apply {
            viewModel = this@MainActivity.viewModel
        }
    }

}
```

## 最終的にしたいコード

最終的にやりたいことはこんな感じだと思います。
よくみるサンプルプロジェクトですね。

```kotlin
// DaggerAppCompatActivityを継承させたい
class MainActivity : DaggerAppCompatActivity() {

    private lateinit var viewModel: MainViewModel
    private lateinit var binding: MainActivityBinding

    // ViewModelFactoryをDIしたい
    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ↓こういう風に書きたい
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity)

        binding.apply {
            viewModel = this@MainActivity.viewModel
        }
    }

}
```

ViewModelFactoryを自作しなければいけないパターンはApplicationクラス以外をコンストラクタの引数としたい場合はDefaultFactoryではない、独自のViewModelProvider.Factoryを用意する必要があるようです。このときにDaggerを使ってDIします。

詳しくは下記の記事にかかれています。

https://qiita.com/satorufujiwara/items/f42b176404287690f1d0


## MainModuleを作る

```kotlin
@Module
internal abstract class MainModule {
    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel
}
```

- `@Binds` の説明 (https://qiita.com/sakuna63/items/1b3e755d6f26908aa8c9)
- `@IntroMap` の説明 (https://qiita.com/m-dove/items/767c4bfaeee53caefc4d)

この実装をこのままコピペすると `@ViewModelKey` がエラーになると思いますのでアノテーションを定義します。

```kotlin
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
```

これでViewModelをDIするためのコードをDaggerに生成してもらいます。

## ActivityModuleを作る

次にActivityに対してどんなModuleをInjectするのか定義します。

```kotlin
@Module
abstract class ActivityModule {

    @ActivityScoped
    @ContributesAndroidInjector(modules = [
        MainModule::class]
    )
    internal abstract fun mainActivity(): MainActivity
}

```

今回は `MainActivity` に対して `MainModule` をInjectするのでその定義をします。

## ViewModelModuleを作る

`ActivityModule` と同様、ViewModelModuleを作成します。

```kotlin
@Module
abstract class ViewModelModule {

    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory):
            ViewModelProvider.Factory
}
```

## ViewModelFactoryを作る

ここまでは `ViewModelModule` でコンパイルエラーが発生すると思うのでここでViewModelFactoryを作成します。

```kotlin
class ViewModelFactory @Inject constructor(
        private val creators: Map<Class<out ViewModel>,
                @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        var creator: Provider<ViewModel>? = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }
        if (creator == null) throw IllegalArgumentException("unknown model class " + modelClass)
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
```

この辺はとりあえずコピペしておけと言いたいのですが、そうはいきません。
さっきも登場した記事ですが、詳しくは下記にかかれていますので読んでください

https://qiita.com/satorufujiwara/items/f42b176404287690f1d0

## AppComponentを作る

次にDaggerの作法なんですが、 `@Component` が付いているインターフェースを実装します。

```kotlin
@Singleton
@Component(
    modules = [
        AndroidSupportInjectionModule::class,
        ActivityModule::class,
        ViewModelModule::class
    ]
)
interface AppComponent : AndroidInjector<MainApplication> {
    @Component.Builder
    abstract class Builder : AndroidInjector.Builder<MainApplication>()
}
```

このインターフェースを作成することによって、 `DaggerAppComponet` クラスがDaggerによって作られます。


## DaggerApplicationを継承したApplicationクラスを設定する

`MainApplication` を作成して `DaggerApplication` クラスを継承します。

```kotlin
class MainApplication : DaggerApplication() {

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.builder().create(this)
    }
}
```

`AndroidManifest.xml` に書くことを忘れずに。


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

    <application
        android:name=".MainApplication" // ←ここを忘れずに
        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>
```

## ViewModelのコンストラクタに `@Inject constructor` をつける

あとはViewModelに対して `@Inject constructor` をつけます。

```kotlin
class MainViewModel @Inject constructor(useCase: MainUseCase) : ViewModel() {
    var test = useCase.getGreeting()
}
```

よくあるのはここのコンストラクタでAPI叩くクラスをインジェクトしたり、ローカルDBにアクセスするクラスをインジェクトしたりするかと思います。

これは最初に書いたように

> ViewModelFactoryを自作しなければいけないパターンはApplicationクラス以外をコンストラクタの引数としたい場合はDefaultFactoryではない、独自のViewModelProvider.Factoryを用意する必要があるようです。このときにDaggerを使ってDIします。

が実現できています。

ちなみにGoogle I/O 2018のコードもそういうようなことをしています。

```kotlin
class MapViewModel @Inject constructor(
    loadMapTileProviderUseCase: LoadMapTileProviderUseCase,
    private val loadGeoJsonFeaturesUseCase: LoadGeoJsonFeaturesUseCase,
    private val analyticsHelper: AnalyticsHelper
) : ViewModel() {

// hogehogeの実装
}
```

## 完成

あとは最初に「こう書きたい！！」って感じで書いていたActivityを書けば完成です。


```kotlin
class MainActivity : DaggerAppCompatActivity() {

    private lateinit var viewModel: MainViewModel
    private lateinit var binding: MainActivityBinding

    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity)

        binding.apply {
            viewModel = this@MainActivity.viewModel
        }
    }

}
```


## 所感

「Daggerはわかりづらい！！」ってよく言われますが、実際やってみると本当にわかりづらい。最初勉強し始めた時にソースコードが追えなさすぎてわけわからなくなりました。

まだ触ってませんが、Koinとかもいいかもしれません。

#### 参考

https://github.com/InsertKoinIO/koin
https://qiita.com/sudachi808/items/8e03503f52b4f11533a2


ここまで書いておいてちゃんと理解できているわけではないのでもし、なんか指摘があれば優しく教えていただけると嬉しいです。