如何在 Compose 中注入 ViewModel?
为什么需要Hilt
- 痛点:在Compose中,我们需要一种优雅的方式来管理状态和依赖,而不是手动传递
- 本文目标就是从配置到实战,掌握Hilt在Compose中的用法
环境配置
要用Hilt第一步就是配置相关环境,但是这一步很容易出错。
developer.android.com/training/de…
上面是Android官网关于《使用 Hilt 实现依赖项注入》的教程
下面进行总结:
- 下面是一个项目的层次
- 其中有两个
build.gradle.kts文件。分别在模块目录下和根目录下 - 我们要在模块的
build.gradle.kts中加入 -
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) // 这是一个代码生成加速器 要使用Hilt依赖注入,通常需要KSP来处理注解生成底层的实现类 id("com.google.devtools.ksp") // Google 官方推荐的依赖注入 (DI) 框架 id("com.google.dagger.hilt.android") // 下面这个是指定数据序列化插件的版本 后面版本和你的kotlin版本一致 kotlin("plugin.serialization") version "2.1.20" } // dependencies中添加hilt库 dependencies { implementation("com.google.dagger:hilt-android:2.57.1") ksp("com.google.dagger:hilt-android-compiler:2.57.1") implementation("androidx.hilt:hilt-navigation-compose:1.0.0") ... } - 根的
build.gradle.kts中加入 -
// 2.1.20 部分必须与你项目使用的 Kotlin 版本 完全匹配,而 -1.0.32 是 KSP 自身的补丁版本。 id("com.google.devtools.ksp") version "2.1.20-1.0.32" apply false id("com.google.dagger.hilt.android") version "2.57.1" apply false - 添加完之后,同步一遍项目,第一次同步时间有点长
核心步骤梳理
-
Hilt的”地基“
- 在项目中创建
Application类并添加@HiltAndroidApp - 接下来在
AndroidManifest.xml中注册
- 在项目中创建
-
入口点
- 在
MainActivity上添加@AndroidEntryPoint - Hilt需要从这里开始注入
- 在
-
下面的部分。我来举一个简单的例子:模拟获取用户信息
-
数据层 (Repository)
-
package com.software.hiltapplication.data.repository import kotlinx.coroutines.delay import javax.inject.Inject interface UserRepository { suspend fun getUserName(): String } // 注意:这里面适用@Inject 让 Hilt 知道如何创建这个类的实例 class UserRepositoryImpl @Inject constructor( ) : UserRepository { override suspend fun getUserName(): String { // 模拟网络延迟 delay(1000) return "Hilt" } }
-
-
依赖注入模块
- 这是 Hilt 的核心部分。我们需要告诉 Hilt:当有人请求
UserRepository接口时,应该提供UserRepositoryImpl的实例 -
package com.software.hiltapplication.di import com.software.hiltapplication.data.repository.UserRepository import com.software.hiltapplication.data.repository.UserRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) // 安装在单例组件中,生命周期跟随App class AppModule { @Provides @Singleton fun provideUserRepository(): UserRepository { return UserRepositoryImpl() } }
- 这是 Hilt 的核心部分。我们需要告诉 Hilt:当有人请求
-
对ViewModel进行改造
- 使用
@HiltViewModel注解ViewModel类。 - 使用
@Inject constructor(...)注入构造函数。 -
package com.software.hiltapplication.ui.user import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.software.hiltapplication.data.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import jakarta.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @HiltViewModel class UserViewModel @Inject constructor( private val userRepository: UserRepository ) : ViewModel() { private val _userName = MutableStateFlow("Loading...") val userName: StateFlow<String> = _userName.asStateFlow() init { loadData() } private fun loadData() { viewModelScope.launch { _userName.value = userRepository.getUserName() } } }
- 使用
-
然后就是界面文件了
- 这是Compose的部分。关键在于
hiltViewModel()来获取ViewModel实例 - 注意:需要在
build.gradle中添加androidx.hilt:hilt-navigation-compose依赖才能使用hiltViewModel()。 -
package com.software.hiltapplication.ui.user import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.software.hiltapplication.data.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import jakarta.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @HiltViewModel class UserViewModel @Inject constructor( private val userRepository: UserRepository ) : ViewModel() { private val _userName = MutableStateFlow("Loading...") val userName: StateFlow<String> = _userName.asStateFlow() init { loadData() } private fun loadData() { viewModelScope.launch { _userName.value = userRepository.getUserName() } } }
- 这是Compose的部分。关键在于
-
这样这个很简单的功能就差不多实现了,我们把MainActivity添加一下吧
-
package com.software.hiltapplication import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Scaffold import androidx.compose.ui.Modifier import com.software.hiltapplication.ui.theme.HiltApplicationTheme import com.software.hiltapplication.ui.user.UserScreen import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { HiltApplicationTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> UserScreen() } } } } }
-
-
然后运行一下代码。看看效果。
-
在上面的代码中
-
ViewModel 不需要知道
UserRepositoryImpl的具体实现,它只关心接口 -
解释为什么在
UserScreen构造函数中默认参数写viewModel: UserViewModel = hiltViewModel()呢,- 这个可以允许你在写 UI 测试或 Preview 时传入模拟的 ViewModel
-
- 有什么问题,欢迎在评论区讨论呀