はじめに
Android開発でDI(Dependency Injection)を導入したいとき、公式が推奨しているのがHiltです。 HiltはDaggerをAndroid向けにシンプル化したライブラリで、セットアップがかなり楽になっています。
本記事では、HiltをAndroidアプリに導入するための基本的な流れをまとめます。
依存関係
// プロジェクトレベル build.gradle.kts
plugins {
id("com.google.dagger.hilt.android") version "2.48" apply false
}
// アプリレベル build.gradle.kts
plugins {
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-android-compiler:2.48")
}
kaptではなくkspを使いたい場合は、Hiltがkspに対応次第乗り換えることができます。
アプリケーションクラスの設定
Hiltを使うには、Applicationクラスに@HiltAndroidAppを付けます。
@HiltAndroidApp
class MyApp : Application()
AndroidManifest.xmlにも登録を忘れずに。
<application android:name=".MyApp" ... />
ActivityとFragmentで使う
@AndroidEntryPoint
class MainActivity : ComponentActivity() { /* ... */ }
@AndroidEntryPointを付けると、そのクラスでフィールドインジェクションやViewModelの注入ができるようになります。
ViewModelへのインジェクション
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
) : ViewModel() { /* ... */ }
Activity/Fragmentからはおなじみのby viewModels()で取得できます。
class MainActivity : ComponentActivity() {
private val viewModel: UserViewModel by viewModels()
}
Moduleで依存を提供する
インターフェースの実装や外部ライブラリのインスタンスなど、@Inject constructorで解決できないものは@Moduleで提供します。
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit =
Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(MoshiConverterFactory.create())
.build()
@Provides
@Singleton
fun provideUserApi(retrofit: Retrofit): UserApi =
retrofit.create(UserApi::class.java)
}
@InstallIn(SingletonComponent::class):アプリ全体でシングルトンとして共有@Provides:インスタンス生成方法を示す@Singleton:スコープ内で単一インスタンス
Interfaceの実装を束ねる
Repositoryなど、インターフェースを実装するクラスがある場合は@Bindsを使います。
interface UserRepository {
suspend fun fetch(id: Long): User
}
class UserRepositoryImpl @Inject constructor(
private val api: UserApi,
) : UserRepository {
override suspend fun fetch(id: Long): User = api.getUser(id)
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}
@Bindsは@Providesより最適化が効くので、単にインターフェースをバインドするだけなら@Bindsを選びます。
Scopeの使い分け
Hiltで代表的なスコープは以下。
| Scope | ライフサイクル |
|---|---|
@Singleton | Application全体 |
@ActivityRetainedScoped | 画面回転を跨いで保持 |
@ActivityScoped | Activityごと |
@ViewModelScoped | ViewModelごと |
@FragmentScoped | Fragmentごと |
Repositoryは基本@Singleton、Use Caseは@ViewModelScoped、のように使い分けるとシンプルです。
Composeへの注入
JetpackComposeではhiltViewModel()が用意されています。
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
// UI
}
よくあるハマりどころ
@HiltAndroidAppの付け忘れ:AppクラスでのDIが動かない- AndroidManifestへの登録忘れ:同上
@AndroidEntryPointを付け忘れる:ViewModelのinjectができない- Moduleの
@InstallInが間違い:スコープ不一致エラー
Hiltはエラーメッセージがかなり親切なので、コンパイルエラーをよく読むと解決できるケースがほとんどです。
おわりに
Hiltはセットアップさえ乗り越えれば、DaggerほどのボイラープレートがなくDIを導入できます。 Androidでテスタブルな設計にしたい場合、個人的には真っ先に入れるライブラリのひとつです。