Dagger2使用時に引数ありのViewModelをinterface化する

# はじめに
本記事はDagger2 + MVVM構成のアーキテクチャで、引数ありのViewModelをinterface化する方法の備忘録です。

# リポジトリ
以下に今回のサンプルを置いてあります。
https://github.com/nanaten/Dagger-ViewModel-Interface

# プロジェクト構成
昨今のAndroid開発における標準的なMVVMを想定しています。

View (Fragment) -> ViewModel -> Repository ( -> API )


# 注意書き
今回、**基本的なDagger2の構成の解説は省略します。** 今回はViewModelに関わる部分のみ解説します。

※Dagger2の基本構成は[【DI】Dagger2+Retrofit2(+OkHttp3)+ViewModelのDIの最小構成](https://qiita.com/m-coder/items/267abba3f059c4c9ed4a)で解説していますのでそちらを参照してください。今回のプロジェクト構成も上記記事とほぼ変わりません。

# 解説
ViewModelは以下のような作りになっています。
`MainViewModelImpl` はコンストラクタに `MainRepository` の引数を持ちます。

```kotlin:MainViewModel
interface MainViewModel {
    ...
}

class MainViewModelImpl @Inject constructor(private val repository: MainRepository) : ViewModel(), MainViewModel {
    ...
}
```

コンストラクタ付きのViewModelを使うために `ViewModelKey` と `ViewModelFactory`を定義します。 `ViewModelFactory` については[こちら](https://qiita.com/satorufujiwara/items/f42b176404287690f1d0)の記事を参考にさせて頂いています。

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

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

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        var creator: Provider<out 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 {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
```

作成したViewModelKeyを使って、ViewModelを以下のようにbindします。

```kotlin
@Binds
@IntoMap
@ViewModelKey(MainViewModelImpl::class)
fun bindMainViewModel(viewModel: MainViewModelImpl): ViewModel
```

bindしたModuleをinjectします。今回はFragmentで使いたかったのでAppComponentに書かずにActivityにmoduleを追加しました。


```kotlin
@Module
interface MainActivityModule {
    @Binds
    @IntoMap
    @ViewModelKey(MainViewModelImpl::class)
    fun bindMainViewModel(viewModel: MainViewModelImpl): ViewModel

    @ContributesAndroidInjector
    fun bindMainFragment(): MainFragment
}
```

```kotlin
@ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun bindMainActivity(): MainActivity
```


そして、FragmentでViewModelProviderを使って以下のように宣言すれば、interface化したViewModelを扱えるようになります。

```kotlin:MainFragment
class MainFragment : DaggerFragment() {
    @Inject
    lateinit var viewModelFactory: ViewModelFactory
    // MainViewModelImpl ではなく MainViewModel として型宣言する
    private val viewModel: MainViewModel by lazy {
        // 実態は MainViewModelImpl を生成する
        ViewModelProvider(this, viewModelFactory).get(MainViewModelImpl::class.java)
    }

}
```
型宣言をちゃんとしないと `MainViewModelImpl` として認識されてしまうのでご注意ください。

ちなみにActivityでViewModelを使う場合もほぼ同じです。Activityで使う場合はModuleをAppComponentに追加してください。

# おわりに
`ViewModelProvider` を使うことに思い至らずに `by viewModels` を使おうとして詰まってました…

本記事を書くにあたって、以下のリンク先を参考にさせて頂きました。
・[[Qiita] ViewModelにLiveDataとMutableLiveDataを同時に宣言しなくていいようにする](https://qiita.com/Pooh3Mobi/items/a04ff2ddf8d7174d6be5)
・[[Qiita] Architecture Components を Dagger2 と併用する際の ViewModelProvider.Factory について](https://qiita.com/satorufujiwara/items/f42b176404287690f1d0)