[Kotlin]既存のViewModelの実装にDagger Hiltを適用する

既存のコードではRoom+LiveDataを利用したViewModelを実装していました。
今回は、そのコードにDagger Hiltを適用する事でDIをする部分のコードが分離できてスッキリしたため、その実装について紹介していきたいと思います。

# Dagger Hiltとは
以下のリンクにあるように、Android用の依存関係インジェクションライブラリとなります。Androidでは基本となるMVVMモデルアーキテクチャのViewModel用のアノテーションとかがあったりするため、非常に簡単に適用する事が出来るようになっています。

https://developer.android.com/training/dependency-injection/hilt-android?hl=ja

# 適用前後でどのくらいスッキリするか
適用前の実装では、Repositoryで利用するためのDataBaseクラスをApplicationクラスで生成したり、ViewModelクラスを生成するためのFactoryクラスを用意する必要があり、以下のような関係性のクラス図となります。

**Dagger Hilt適用前クラス図**
![クラス図_ViewModel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545703/18115773-79f2-a9ff-c72f-195835a7e175.png)

Dagger Hiltを適用する事で、Applicationクラスでの生成処理やViewModelを生成するためのFactoryクラスが不要となり、代わりに専用のアノテーションを付けたModuleクラスに生成方法実装するだけで適用できるため以下のような関係性のクラス図となります。

**Dagger Hilt適用後クラス図**
![クラス図_DaggerHilt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545703/2c17b959-8521-58ca-4588-2e6ca2c9a7b1.png)
次章からは、具体的なDagger Hiltの実装方法について書いていきたいと思います。

# Dagger Hiltライブラリの追加
プロジェクトのbuild.gradleには、ライブラリのクラスパスを追加します。
```build.gradle
buildscript {
    dependencies {
        def hilt_version = "2.45"
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
```
次に、app/build.gradleにはライブラリをimplementationします。
```build.gradle
dependencies {
    def hilt_version = "2.45"
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
```

# Hiltのコード生成トリガーとなるアノテーションを設定
Applicationクラスを継承したクラスに`@HiltAndroidApp`アノテーションを追加します。
また、Applicationクラスで生成していた`ComicMemoRoomDatabase`や`ComicMemoRepository`の生成する実装は記載不要となるため、以下のような修正となります。
**修正前**
```ComicMemoApplication.kt
class ComicMemoApplication : Application() {
    val database by lazy { ComicMemoRoomDatabase.getDatabase(this) }
    val repository by lazy { ComicMemoRepository(database.comicDao()) }
}
```
**修正後**
```ComicMemoApplication.kt
@HiltAndroidApp
class ComicMemoApplication : Application() {}
```

# 依存するクラスにエントリポイントを設定
Dagger Hiltを適用する上で依存するクラスには`@AndroidEntryPoint`アノテーションを設定する事で明示する必要があります。今回の場合には、`ComicMemoActivity`が依存するため、以下のように修正します。
```ComicMemoActivity.kt
@AndroidEntryPoint
class ComicMemoActivity : AppCompatActivity(), SectionsPagerAdapter.SectionPagerAdapterListener {
    // etc...
}
```

# ViewModelのFactoryクラスの削除
MVVMモデルにおいてViewModelを生成する場合には、Factoryクラスを用意してViewModelの生成を行いますが、Dagger Hiltを適用することでその実装は不要となるため削除します。
また、引数で必要となるリポジトリについては、`@Inject`アノテーションを設定する事で明示します。
以下のように修正します。

**修正前**
```PlaceHolderFragment.kt
class PlaceholderFragment : Fragment(), AdapterListener {
    override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
        return ComicPagerViewModel.Factory((activity?.application as ComicMemoApplication).repository)
    }
    // etc...
}
```
```ComicPagerViewModel.kt
class ComicPagerViewModel(private val repository: ComicMemoRepository) : ViewModel() {
    class Factory(private val repository: ComicMemoRepository) : ViewModelProvider.NewInstanceFactory() {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return ComicPagerViewModel(repository) as T
        }
    }
    // etc...
}
```

**修正後**
```PlaceHolderFragment.kt
@AndroidEntryPoint
class PlaceholderFragment : Fragment(), AdapterListener {
    // etc...
}
```
```ComicPagerViewModel.kt
@HiltViewModel
class ComicPagerViewModel @Inject constructor(private val repository: ComicMemoRepository) : ViewModel() {
    // etc...
}
```

# 引数のDaoに対して注入が必要であることを明示する
リポジトリの引数で渡す`Dao`に対して、`@Inject`アノテーションを付ける事で依存性の注入をすることを明示するため、以下のように修正します。

**修正前**
```ComicMemoRepository.kt
class ComicMemoRepository(private val comicDao: ComicDao) {
    // etc...
}
```

**修正後**
```ComicMemoRepository.kt
class ComicMemoRepository @Inject constructor(private val comicDao: ComicDao) {
    // etc...
}
```

# Databaseクラスから生成処理を削除する
Databaseクラスの生成はHiltモジュールから生成するため、削除します。

**修正前**
```ComicMemoRoomDatabase.kt
abstract class ComicMemoRoomDatabase : RoomDatabase() {
    abstract fun comicDao(): ComicDao

    companion object {
        @Volatile
        private var INSTANCE: ComicMemoRoomDatabase? = null

        fun getDatabase(
            context: Context,
        ): ComicMemoRoomDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    ComicMemoRoomDatabase::class.java,
                    "ComicMemoDB"
                )
                .build()
                INSTANCE = instance
                instance
            }
        }
    }
}
```

**修正後**
```ComicMemoRoomDatabase.kt
```ComicMemoRoomDatabase.kt
abstract class ComicMemoRoomDatabase : RoomDatabase() {
    abstract fun comicDao(): ComicDao
}
```

# Hiltモジュールの実装
`@Module`アノテーションをつけたモジュールクラスを実装する事で、インスタンスの提供方法をHiltに明示する事ができます。
なので、今回は`Database`と`Dao`の生成方法を明示する必要があるため、以下のような実装をします。
```ComicMemoModule.kt
@Module
@InstallIn(SingletonComponent::class)
object ComicMemoModule {

    @Singleton
    @Provides
    fun provideDatabase(
        @ApplicationContext context: Context
    ) = Room.databaseBuilder(context, ComicMemoRoomDatabase::class.java, "ComicMemoDB").build()

    @Singleton
    @Provides
    fun provideComicDao(db: ComicMemoRoomDatabase) = db.comicDao()
}
```
メソッドに`@Provides`アノテーションを付けることで、インスタンスを提供するメソッドである事を明示し、`@Singleton`アノテーションを付けることで、シングルトンなインスタンスを提供するようにします。
以上で完成です！

# さいごに
Dagger Hiltを適用したアプリのコードはMITライセンスで公開していますので、今回説明した部分だけでなく全体のコードを参照する事ができます。

https://github.com/highcom/ComicMemo

ぜひこちらのプロジェクトも参考にしてみて下さい！！