【DI】Dagger2+Retrofit2(+OkHttp3)+ViewModelのDIの最小構成[その2]

# はじめに
この記事は[【DI】Dagger2+Retrofit2(+OkHttp3)+ViewModelのDIの最小構成[その1]](https://qiita.com/m-coder/items/267abba3f059c4c9ed4a)の続きです。
今回はRetrofitとViewModelをDIしていきます。

~~2019/12/26 追記
**どうやら最新バージョンのDagger(2.25.3)で本記事のやり方でDIしようとすると、複数のViewModelを生成するとエラーとなってしまうようです。**
2.24では問題ないです。最新版での良いやり方を見つけたら本記事に追記します。~~
2019/12/27 追記 
2.25.3でも問題なかったです。変なところに`Named`を付けてエラーが出てました。
ただ、`@Component.Builder`を使ったやり方は若干古かったようなので、[[その１]](https://qiita.com/m-coder/items/267abba3f059c4c9ed4a)と併せて記述を修正しました。

## 4. Retrofitの依存性注入
ApiクラスとApiModuleを追加します。
ベースURLはGitHubのAPIに指定してます。

```kotlin:Api.kt
interface Api
```

```kotlin:ApiModule.kt

@Module
class ApiModule {
    companion object {
        const val API_READ_TIMEOUT: Long = 10
        const val API_CONNECT_TIMEOUT: Long = 10
    }

    @Provides
    @Singleton
    fun provideOkhttpClient(): OkHttpClient {
        val logInterceptor = HttpLoggingInterceptor()
        logInterceptor.level = HttpLoggingInterceptor.Level.BODY

        return OkHttpClient.Builder()
            .addInterceptor {
                val httpUrl = it.request().url
                val requestBuilder = it.request().newBuilder().url(httpUrl)
                it.proceed(requestBuilder.build())
            }
            .addInterceptor(logInterceptor)
            .readTimeout(API_READ_TIMEOUT, TimeUnit.SECONDS)
            .connectTimeout(API_CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        val moshi = Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl("https://api.github.com/")
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideAPI(retrofit: Retrofit): Api {
        return retrofit.create(Api::class.java)
    }
}
```
~~JSONのパース用に[gson](https://github.com/google/gson)を使っています。
今回の主題ではないので詳細は省略します。~~
**2020/01/07追記**
[JakeWharton](https://github.com/JakeWharton)氏曰く[「Gson is deprecated.」](https://www.reddit.com/r/androiddev/comments/ejjroe/kotlinx_json_vs_gson_a_short_article_about_kotlin/fcz3v0d/?utm_source=share&utm_medium=web2x)との事だったので、Gsonから[Moshi](https://github.com/square/moshi)に変更しました。

AppComponentにApiModuleの依存性を追加します。

**2020/03/29追記**
ApiModuleを不要に `BindsInstance` していたため、該当コードを削除しました。

```kotlin:AppComponent.kt

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        MainActivityBuilder::class,
        ApiModule::class    // 追加
    ]
)

interface AppComponent : AndroidInjector<App> {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance app: App): AppComponent
    }
}
```

## 5. ViewModel(ViewModelFactory)の依存性を注入する
引数付きのViewModelを扱うために、`ViewModelFactory`を作成します。
@satorufujiwara さんの[こちらの記事](https://qiita.com/satorufujiwara/items/f42b176404287690f1d0)を参考にさせて頂きました。

```kotlin:ViewModelFactory.kt
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)
        }
    }
}
```

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

続けてViewModelを作成します。
今回はMainFragmentから呼び出すMainViewModelを実装します。

```kotlin:MainViewModel
class MainViewModel @Inject constructor(private val useCase: MainUseCase): ViewModel()
```

MainViewModelからUseCaseを呼び出したいため、コンストラクタでMainUseCaseをInjectしています。
ちなみにMainUseCaseではMainRepositoryをInjectし、MainRepositoryではApiをInjectする想定です。

```kotlin:MainUseCase
class MainUseCase @Inject constructor(private val repository: MainRepository) 
```

```kotlin:MainRepository
class MainRepository @Inject constructor(private val api: Api)
```

ViewModelをBindします。
MainFragmentと同じScopeにするため、MainFragmentModuleに追加しています。


```kotlin:MainFragmentModule.kt
@Module
internal abstract class MainFragmentModule {
    @ContributesAndroidInjector
    @FragmentScope
    abstract fun provideMainFragment(): MainFragment

    // 追加
    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    @FragmentScope
    internal abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel
}
```

最後です。
MainViewModelでViewModelのインスタンス取得処理を書きます。
この時ViewModelFactoryを指定してあげます。

```kotlin:MainFragment
class MainFragment : DaggerFragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelFactory
    lateinit var viewModel: MainViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
        return inflater.inflate(R.layout.fragment_main, container, false)
    }
```

これでひとまず完成です。
正常にビルドが通っていればDIが成功しています。

## 余談1 UseCase、Repositoryをinterface化する
上記の記事ではUseCase、Repositoryを直接DIしましたが、
私の場合、UseCaseやRepositoryはinterfaceとして実装し、実際の処理はimplementsしたクラスに記載する事が多いです。

```kotlin:MainUseCase※MainRepositoryも同様
interface MainUseCase {
    fun hoge()
}

class MainUseCaseImpl @Inject constructor(private val repository: MainRepository): MainUseCase {
    override fun hoge() {
         〜実際の処理〜
    }
}
```

その場合、下記のようにDIを行います。

*ここがいつも悩んでいるところで、AppComponentにModuleを記載するのが良いのか、MainActivityBuilderにModuleを追加する形の方が良いのかよく分かってないのです。
Daggerに詳しい方いましたらご意見ください。*

```kotlin:MainModule.kt
@Module
internal object MainModule {
    @Singleton
    @Provides
    @JvmStatic
    fun provideMainRepository(api: Api): MainRepository =
        MainRepositoryImpl(api)

    @Singleton
    @Provides
    @JvmStatic
    fun provideMainUseCase(repository: MainRepository): MainUseCase =
        MainUseCaseImpl(repository)
}
```

```kotlin:AppComponent.kt
@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ApiModule::class,
        MainActivityBuilder::class,
        MainModule::class // 追加
    ]
)
```

## 余談2 ViewModelのインスタンス化について
上記記事では`lateinit var`を使いましたが、最近はAndroid-KTXを使うことによって、下記のように書けるようになったようです。

```groovy@build.gradle
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc04' // 追加
```

```kotlin
private val viewModel: MainViewModel by viewModels()
```

ViewModelFactoryを使う場合は下記のように書けます。

```kotlin:MainFragment
private val viewModel: MainViewModel by viewModels { viewModelFactory }
```

valになり、1行で書けるようになったのでスッキリしましたね。

# 最後に
記事を書くにあたって以下を参考にさせて頂きました。
Daggerは本当に難しい。。。
・[Dagger2 + Retrofit2 + Moshi + Kotlin を使って通信するまで](https://qiita.com/shimizu-you/items/216d875b49742f73eb86)
・[Dagger2: 2.23に入ったHasAndroidInjectorについて](https://satoshun.github.io/2019/06/dagger-android-injector/)
・[Architecture Components を Dagger2 と併用する際の ViewModelProvider.Factory について](https://qiita.com/satorufujiwara/items/f42b176404287690f1d0)
・[Fragment の Android-KTX が AAC ViewModel の取得に便利だ](https://qiita.com/mangano-ito/items/9b067916d1374d66b750)