3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Android】Hiltを使用してRoomのCodelabソースコードにDI風にリファクタリングした話

Posted at

はじめに

皆さん、ごきげんよう!れぶです!

今回の記事では、公式チュートリアル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のイメージ図

hilt___-1.png

実装方法

1.build.gradleファイルに追加

執筆時点でのHiltのバージョンは2.44ですが、適宜変更してください。

build.gradle(project)
plugins {
    id 'com.google.dagger.hilt.android' version '2.44' apply false
}
build.gradle(app)
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定義

リファクタリング前

WordsApplication.kt
class WordsApplication : Application() {
    val database by lazy { WordRoomDatabase.getDatabase(this) }
    val repository by lazy { WordRepository(database.wordDao()) }
}

ApplicationクラスでRoomDatabaseRepositoryのインスタンスを生成しています。これらは「6.(リファクタリング前)」で使用します。

リファクタリング後

WordsApplication.kt
@HiltAndroidApp
class WordsApplication : Application() {}

Applicationクラスに@HiltAndroidAppを付けることで、アプリでHiltを使用できるように設定します。RoomDatabaseとRepositoryのインスタンスは後に生成・注入するので、中身は空っぽで良いです。

3.Hiltモジュール作成

リファクタリング前

WordRoomDatabase.kt
@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を作成・返します。

リファクタリング後

WordRoomDatabase.kt
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
   abstract fun wordDao(): WordDao
}

上記companion object内の中身は次のRoomModuleで引き継ぐため、削除します。

RoomModule.kt
@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を付けた関数により、関数のインスタンスをシングルトンとして提供します。今回はRoomDatabaseDAOのインスタンスの二つを提供します。

4.Repositoryに依存関係を注入

リファクタリング前

WordRepository.kt
class WordRepository(private val wordDao: WordDao) {
   (省略)
}

リファクタリング後

WordRepository.kt
class WordRepository @Inject constructor(private val wordDao: WordDao) {
    (省略)
}

「3.」のHiltモジュールから持ってきたDAOインスタンスをRepositoryに注入します。注入方法は、@InjectによるConstructor Injectionです。

5.ViewModelに依存関係を注入

リファクタリング前

WordViewModel.kt
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内でコンストラクタの引数を使用できます。

リファクタリング後

WordViewModel.kt
@HiltViewModel
class WordViewModel @Inject constructor(
    private val repository: WordRepository) : ViewModel() {
   (省略)
}

ViewModelに依存関係を注入する場合、@HiltViewModelを付けます。ViewModelに「4.」のRepositoryをConstructor Injectionで注入していきます。勿論Factoryの定義は必要ありません。

6.Activityに依存関係を注入

リファクタリング前

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val wordViewModel: WordViewModel by viewModels {
        WordViewModelFactory((application as WordsApplication).repository)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        (省略)
    }
}

「2.(リファクタリング前)」で生成したRepositoryインスタンスを使って、ViewModelを作成します。

リファクタリング後

MainActivity.kt
@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風に仕上げました。参考までに。

参考サイト

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?