はじめに
皆さん、ごきげんよう!れぶです!
今回の記事では、公式チュートリアルCodelabの「Android Room with a View - Kotlin」で扱うコードをHilt
でDIしていきます。
上記のCodelabでは、英単語のToDoアプリを作りながらRoom
周辺の使い方を学べます。全体的にDIパターンを採用していますが、今回はHiltを使用してDIを自動化します。
それでは、参りましょう!!
この記事の対象者
- DIの概念やHiltの使い方を多少理解している方
- (かつ)上記のCodelabを参照して実装したコードをHiltを使ってリファクタリングしたい方
開発環境
- MacBook Air
- Android Studio Electric Eel | 2022.1.1
- Kotlin
- compileSdkVersion 33
- minSdkVersion 21
DIのイメージ図
実装方法
1.build.gradleファイルに追加
執筆時点でのHiltのバージョンは2.44ですが、適宜変更してください。
plugins {
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
plugins {
id 'com.google.dagger.hilt.android'
}
dependencies {
//Hilt
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
}
kapt {
correctErrorTypes true
}
2.ApplicationクラスにHilt定義
リファクタリング前
class WordsApplication : Application() {
val database by lazy { WordRoomDatabase.getDatabase(this) }
val repository by lazy { WordRepository(database.wordDao()) }
}
ApplicationクラスでRoomDatabase
とRepository
のインスタンスを生成しています。これらは「6.(リファクタリング前)」で使用します。
リファクタリング後
@HiltAndroidApp
class WordsApplication : Application() {}
Applicationクラスに@HiltAndroidApp
を付けることで、アプリでHiltを使用できるように設定します。RoomDatabaseとRepositoryのインスタンスは後に生成・注入するので、中身は空っぽで良いです。
3.Hiltモジュール作成
リファクタリング前
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
instance
}
}
}
}
シングルトンとして定義したcompanion object内のgetDatabase()
で、RoomDatabase
を作成・返します。
リファクタリング後
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
}
上記companion object内の中身は次のRoomModule
で引き継ぐため、削除します。
@Module
@InstallIn(SingletonComponent::class)
object RoomModule {
@Singleton
@Provides
fun provideDatabase(
@ApplicationContext context: Context
) = Room.databaseBuilder(context, WordRoomDatabase::class.java, "word_database").build()
@Singleton
@Provides
fun provideDao(db: WordRoomDatabase) = db.wordDao()
}
objectに@Module
と@InstallIn
を付けることで、Hiltモジュール作成します。Hiltモジュールはコンストラクタ注入ができない型を含めて様々なインスタンスの提供方法をHiltに伝えます。
@Singleton
と@Provides
を付けた関数により、関数のインスタンスをシングルトンとして提供します。今回はRoomDatabase
とDAO
のインスタンスの二つを提供します。
4.Repositoryに依存関係を注入
リファクタリング前
class WordRepository(private val wordDao: WordDao) {
(省略)
}
リファクタリング後
class WordRepository @Inject constructor(private val wordDao: WordDao) {
(省略)
}
「3.」のHiltモジュールから持ってきたDAOインスタンスをRepositoryに注入します。注入方法は、@Inject
によるConstructor Injectionです。
5.ViewModelに依存関係を注入
リファクタリング前
class WordViewModel(private val repository: WordRepository) : ViewModel() {
(省略)
}
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
(省略)
}
}
独自のFactory
を定義することで、ViewModel内でコンストラクタの引数を使用できます。
リファクタリング後
@HiltViewModel
class WordViewModel @Inject constructor(
private val repository: WordRepository) : ViewModel() {
(省略)
}
ViewModelに依存関係を注入する場合、@HiltViewModel
を付けます。ViewModelに「4.」のRepositoryをConstructor Injectionで注入していきます。勿論Factoryの定義は必要ありません。
6.Activityに依存関係を注入
リファクタリング前
class MainActivity : AppCompatActivity() {
private val wordViewModel: WordViewModel by viewModels {
WordViewModelFactory((application as WordsApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
(省略)
}
}
「2.(リファクタリング前)」で生成したRepository
インスタンスを使って、ViewModelを作成します。
リファクタリング後
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val wordViewModel: WordViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
(省略)
}
}
Activityに依存関係を注入する場合、@AndroidEntryPoint
を付けます。Activityに「5.」のViewModelをby viewModels()
で注入していきます。
おわりに
今回は公式チュートリアルCodelabのRoom編で扱うコードを、Hilt
を使ったプログラムへリファクタリングする方法を書きました。
Hilt導入前では、ApplicationクラスやFactoryなど手動で依存関係を設定する必要がありました。しかし導入後では、アノテーションでHiltに指示をすることで自動で依存関係を把握し、注入してくれます。楽ですよね。
以上です。ありがとうございました!
おまけ
上記の方法をもとに、Android開発で習得・使用した技術を管理できる簡単なサンプルアプリをDI風に仕上げました。参考までに。