💨

Dagger 2からHiltへの移行

2024/08/05に公開

はじめに

Android開発では、アプリケーションの構造を整え、依存性の管理を簡素化するためにMVVM(Model-View-ViewModel)パターンとDI(Dependency Injection)パターンが広く使われています。今回は、Jetpack Composeを使用してこれらのパターンを導入する方法を紹介し、特にDagger 2からHiltへの移行手順に焦点を当てます。

Dagger 2を使用した設定

まず、Dagger 2を使用した場合の設定を示します。

必要なライブラリ

build.gradleファイルに以下の依存関係を追加します。

build.gradle(App)
dependencies {
    // Jetpack Compose
    implementation "androidx.compose.ui:ui:1.0.0"
    implementation "androidx.compose.material:material:1.0.0"
    implementation "androidx.compose.ui:ui-tooling-preview:1.0.0"
    implementation "androidx.activity:activity-compose:1.3.0"

    // Dagger 2
    implementation 'com.google.dagger:dagger:2.37'
    kapt 'com.google.dagger:dagger-compiler:2.37'

    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"

    // Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
}

Dagger 2設定

AppModule.kt
@Module
object AppModule {

    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return RepositoryImpl()
    }

    @Provides
    fun provideViewModelFactory(repository: Repository): ViewModelProvider.Factory {
        return ViewModelFactory(repository)
    }
}
AppComponent.kt
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: Application): AppComponent
    }
}
MyApplication.kt
class MyApplication : Application() {
    val appComponent: AppComponent by lazy {
        DaggerAppComponent.factory().create(this)
    }
}
Repository.kt
interface Repository {
    suspend fun fetchData(): List
}
RepositoryImpl.kt

class RepositoryImpl : Repository {
    override suspend fun fetchData(): List {
        return listOf("Data 1", "Data 2", "Data 3")
    }
}
MyViewModel.kt
class MyViewModel(private val repository: Repository) : ViewModel() {
    private val _data = MutableLiveData>()
    val data: LiveData> get() = _data

    init {
        viewModelScope.launch {
            _data.value = repository.fetchData()
        }
    }
}

class ViewModelFactory(private val repository: Repository) : ViewModelProvider.Factory {
    override fun  create(modelClass: Class): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
MainActivity.kt
class MainActivity : ComponentActivity() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private val viewModel: MyViewModel by viewModels { viewModelFactory }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (applicationContext as MyApplication).appComponent.inject(this)
        setContent {
            MyApp(viewModel)
        }
    }
}
MyApp.kt
@Composable
fun MyApp(viewModel: MyViewModel) {
    val data by viewModel.data.observeAsState(initial = emptyList())
    
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            data.forEach { item ->
                Text(text = item, style = MaterialTheme.typography.h5)
            }
        }
    }
}

Hiltへの移行

Hiltに置き換える手順を以下に示します。HiltはDagger 2の上に構築されており、Android開発向けに最適化されています。設定が簡単で、注入ポイントを簡潔に定義できます。

必要なライブラリ

build.gradleファイルにHiltの依存関係を追加します。

build.gradle(app)
dependencies {
    // Jetpack Compose
    implementation "androidx.compose.ui:ui:1.0.0"
    implementation "androidx.compose.material:material:1.0.0"
    implementation "androidx.compose.ui:ui-tooling-preview:1.0.0"
    implementation "androidx.activity:activity-compose:1.3.0"

    // Hilt
    implementation "com.google.dagger:hilt-android:2.37"
    kapt "com.google.dagger:hilt-android-compiler:2.37"

    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"

    // Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
}

プロジェクトのトップレベルのbuild.gradleファイルに以下を追加し、アプリレベルのbuild.gradleファイルにHiltプラグインを適用します。

build.gradle(project)
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.37'
    }
}
apply plugin: 'dagger.hilt.android.plugin'

Hiltの設定

MyApplication.ktファイルを以下のように変更します。

MyApplication.kt
@HiltAndroidApp
class MyApplication : Application()

MainActivity.ktファイルを以下のように変更します。

MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp(viewModel)
        }
    }
}

Hiltのモジュールとして設定するために、AppModule.ktファイルを以下のように変更します。

AppModule.kt
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return RepositoryImpl()
    }
}

Hiltを使用してViewModelの依存関係を注入するために、以下のように変更します。

MyViewModel.kt
@HiltViewModel
class MyViewModel @Inject constructor(


    private val repository: Repository
) : ViewModel() {
    private val _data = MutableLiveData>()
    val data: LiveData> get() = _data

    init {
        viewModelScope.launch {
            _data.value = repository.fetchData()
        }
    }
}

UIのコードは変更しなくても大丈夫です。

MyApp.kt
@Composable
fun MyApp(viewModel: MyViewModel) {
    val data by viewModel.data.observeAsState(initial = emptyList())
    
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            data.forEach { item ->
                Text(text = item, style = MaterialTheme.typography.h5)
            }
        }
    }
}

AppComponent.ktは不要のため削除してください。

まとめ

Dagger 2を使用した場合、設定が複雑で学習コストが高いことがありますが、Hiltを使用することで、これらの設定を簡略化し、Android開発における依存性注入をよりシンプルに行うことができます。今回紹介した方法を参考にして、Jetpack ComposeとMVVMパターン、リポジトリパターンを組み合わせた効率的なアプリケーション開発を進めてみてください。

参考文献

Jetpack Compose
Hilt

多角的な反証

Hiltの制限: 特定のケースではカスタマイズが難しい場合があります。
MVVMの学習コスト: 既存のアーキテクチャからの移行に時間がかかる可能性があります。

このブログ記事を通じて、Jetpack Composeを活用したMVVMパターンとDIパターンの導入方法が理解できることを願っています。

Discussion