既存のコードではRoom+LiveDataを利用したViewModelを実装していました。
今回は、そのコードにDagger Hiltを適用する事でDIをする部分のコードが分離できてスッキリしたため、その実装について紹介していきたいと思います。
Dagger Hiltとは
以下のリンクにあるように、Android用の依存関係インジェクションライブラリとなります。Androidでは基本となるMVVMモデルアーキテクチャのViewModel用のアノテーションとかがあったりするため、非常に簡単に適用する事が出来るようになっています。
適用前後でどのくらいスッキリするか
適用前の実装では、Repositoryで利用するためのDataBaseクラスをApplicationクラスで生成したり、ViewModelクラスを生成するためのFactoryクラスを用意する必要があり、以下のような関係性のクラス図となります。
Dagger Hiltを適用する事で、Applicationクラスでの生成処理やViewModelを生成するためのFactoryクラスが不要となり、代わりに専用のアノテーションを付けたModuleクラスに生成方法実装するだけで適用できるため以下のような関係性のクラス図となります。
Dagger Hilt適用後クラス図
次章からは、具体的なDagger Hiltの実装方法について書いていきたいと思います。
Dagger Hiltライブラリの追加
プロジェクトのbuild.gradleには、ライブラリのクラスパスを追加します。
buildscript {
dependencies {
def hilt_version = "2.45"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
次に、app/build.gradleにはライブラリをimplementationします。
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
の生成する実装は記載不要となるため、以下のような修正となります。
修正前
class ComicMemoApplication : Application() {
val database by lazy { ComicMemoRoomDatabase.getDatabase(this) }
val repository by lazy { ComicMemoRepository(database.comicDao()) }
}
修正後
@HiltAndroidApp
class ComicMemoApplication : Application() {}
依存するクラスにエントリポイントを設定
Dagger Hiltを適用する上で依存するクラスには@AndroidEntryPoint
アノテーションを設定する事で明示する必要があります。今回の場合には、ComicMemoActivity
が依存するため、以下のように修正します。
@AndroidEntryPoint
class ComicMemoActivity : AppCompatActivity(), SectionsPagerAdapter.SectionPagerAdapterListener {
// etc...
}
ViewModelのFactoryクラスの削除
MVVMモデルにおいてViewModelを生成する場合には、Factoryクラスを用意してViewModelの生成を行いますが、Dagger Hiltを適用することでその実装は不要となるため削除します。
また、引数で必要となるリポジトリについては、@Inject
アノテーションを設定する事で明示します。
以下のように修正します。
修正前
class PlaceholderFragment : Fragment(), AdapterListener {
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return ComicPagerViewModel.Factory((activity?.application as ComicMemoApplication).repository)
}
// etc...
}
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...
}
修正後
@AndroidEntryPoint
class PlaceholderFragment : Fragment(), AdapterListener {
// etc...
}
@HiltViewModel
class ComicPagerViewModel @Inject constructor(private val repository: ComicMemoRepository) : ViewModel() {
// etc...
}
引数のDaoに対して注入が必要であることを明示する
リポジトリの引数で渡すDao
に対して、@Inject
アノテーションを付ける事で依存性の注入をすることを明示するため、以下のように修正します。
修正前
class ComicMemoRepository(private val comicDao: ComicDao) {
// etc...
}
修正後
class ComicMemoRepository @Inject constructor(private val comicDao: ComicDao) {
// etc...
}
Databaseクラスから生成処理を削除する
Databaseクラスの生成はHiltモジュールから生成するため、削除します。
修正前
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
abstract class ComicMemoRoomDatabase : RoomDatabase() {
abstract fun comicDao(): ComicDao
}
Hiltモジュールの実装
@Module
アノテーションをつけたモジュールクラスを実装する事で、インスタンスの提供方法をHiltに明示する事ができます。
なので、今回はDatabase
とDao
の生成方法を明示する必要があるため、以下のような実装をします。
@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ライセンスで公開していますので、今回説明した部分だけでなく全体のコードを参照する事ができます。
ぜひこちらのプロジェクトも参考にしてみて下さい!!