25、依赖注入:Hilt 在 Compose 中的集成、hiltViewModel()
依赖注入,说白了就是「把对象创建的权利交出去」。我刚开始写 Android 时,习惯在 Activity 里 new 一堆对象,结果项目一复杂,改个构造参数能改到崩溃。后来用了 Dagger,又觉得配置太繁琐。直到 Hilt 出现,我才觉得依赖注入终于「接地气」了。
这一章,我们就聊聊 Hilt 在 Compose 中的集成,以及那个神奇的 hiltViewModel() 函数。
为什么需要 Hilt?
你想想看,一个典型的 Android 应用,ViewModel、Repository、ApiService、DatabaseDao……这些对象之间有着复杂的依赖关系。如果手动创建,不仅代码冗余,而且难以测试。Hilt 帮你自动管理这些对象的生命周期和作用域,你只需要告诉它「我需要什么」,它就会把现成的对象递给你。
我在项目中遇到过最典型的场景:一个 ViewModel 依赖两个 Repository,每个 Repository 又依赖不同的 ApiService 和 DatabaseDao。如果不用 Hilt,光构造函数的参数传递就能写几十行。用了 Hilt 之后,代码清爽多了。
Hilt 在 Compose 中的集成步骤
集成 Hilt 其实不复杂,但有几个关键点需要注意。我按步骤来拆解:
1. 添加依赖
首先,在项目级的 build.gradle 中添加 Hilt 插件:
// 项目级 build.gradle
plugins {
id 'com.google.dagger.hilt.android' version '2.48' apply false
}
然后在模块级的 build.gradle 中应用插件并添加依赖:
// 模块级 build.gradle
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
dependencies {
implementation "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-compiler:2.48"
// Compose 集成
implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
}
注意:Hilt 版本需要与你的 AGP 版本兼容。我曾经因为版本不匹配,编译时卡了整整半天。建议去官方文档查看兼容性矩阵。
2. 创建 Application 类并添加 @HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化工作
}
}
这个注解会触发 Hilt 的代码生成,生成一个基类。嗯,这一步不能省略。
3. 为 Activity 添加 @AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
MyScreen()
}
}
}
}
只有添加了 @AndroidEntryPoint 的组件,才能使用 Hilt 注入。Activity、Fragment、Service、BroadcastReceiver 都支持。
hiltViewModel() 的使用
这是 Compose 中最常用的 Hilt 函数。它让你在 Composable 函数中直接获取 ViewModel,而且 ViewModel 的依赖会自动注入。
看一个完整的例子:
// 1. 定义 Repository
class UserRepository @Inject constructor(
private val apiService: ApiService
) {
suspend fun getUser(): User {
return apiService.fetchUser()
}
}
// 2. 定义 ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _userState = mutableStateOf<User?>(null)
val userState: State<User?> = _userState
fun loadUser() {
viewModelScope.launch {
val user = repository.getUser()
_userState.value = user
}
}
}
// 3. 在 Composable 中使用
@Composable
fun UserScreen(
viewModel: UserViewModel = hiltViewModel()
) {
val user = viewModel.userState.value
Column {
user?.let {
Text("用户名: ${it.name}")
Text("邮箱: ${it.email}")
}
Button(onClick = { viewModel.loadUser() }) {
Text("加载用户")
}
}
}
核心要点:
- ViewModel 类上添加
@HiltViewModel注解 - 构造函数使用
@Inject constructor - 在 Composable 中直接调用
hiltViewModel()
作用域与生命周期
Hilt 提供了多种作用域,控制对象的生命周期。在 Compose 中,最常用的是 @ViewModelScoped 和 @ActivityScoped。
| 作用域 | 生命周期 | 适用场景 |
|---|---|---|
| @ViewModelScoped | 跟随 ViewModel | Repository、UseCase 等 |
| @ActivityScoped | 跟随 Activity | SharedPreferences、数据库实例等 |
| @Singleton | 整个应用 | Retrofit、OkHttpClient 等 |
举个例子:
@Module
@InstallIn(ViewModelComponent::class)
object UserModule {
@Provides
@ViewModelScoped
fun provideUserRepository(
apiService: ApiService
): UserRepository {
return UserRepository(apiService)
}
}
这样,UserRepository 的生命周期就和 ViewModel 绑定了。当 ViewModel 被销毁时,Repository 也会被释放。
避坑指南
我曾经踩过几个坑,分享给你:
- hiltViewModel() 只能在 @AndroidEntryPoint 的组件中使用:如果你在普通的 Composable 中调用,会报错。确保你的 Activity 或 Fragment 添加了注解。
- ViewModel 的构造函数参数必须全部可注入:如果某个参数 Hilt 不知道如何创建,编译就会失败。检查所有依赖是否都添加了
@Inject或@Provides。 - 不要手动创建 ViewModel:使用
hiltViewModel()而不是viewModel(),否则依赖注入不会生效。
小技巧:如果你需要在 Composable 中获取 Application 实例,可以使用 LocalContext.current.applicationContext。但更推荐的做法是,通过 Hilt 注入一个 Application 级别的依赖。
知识体系图
下面这张图展示了 Hilt 在 Compose 中的核心流程:
总结
Hilt 在 Compose 中的集成,说白了就是三步:加注解、写依赖、用 hiltViewModel()。我个人习惯把所有的依赖定义放在一个单独的 Module 中,这样管理起来更清晰。记住,依赖注入不是为了炫技,而是为了让代码更干净、更可测试。
如果你在集成过程中遇到问题,先检查注解是否齐全,再检查依赖是否都正确声明。大部分问题都出在这两个地方。
公众号:蓝海资料掘金营,微信deep3321