在 Jetpack Compose 中,ViewModel 是管理 UI 状态和业务逻辑的核心组件。它与 Compose 的响应式编程模型完美契合,下面是详细的整合方法和最佳实践。
基本集成方式
1. 添加必要依赖
dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
implementation 'androidx.compose.runtime:runtime-livedata:1.5.4'
}
2. 创建 ViewModel
class CounterViewModel : ViewModel() {
// 使用 StateFlow 管理状态
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value += 1
}
fun reset() {
_count.value = 0
}
}
3. 在 Compose 中使用 ViewModel
@Composable
fun CounterScreen() {
// 获取 ViewModel 实例
val viewModel: CounterViewModel = viewModel()
// 将 StateFlow 转换为 Compose 状态
val count by viewModel.count.collectAsState()
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "计数: $count", style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { viewModel.increment() }) {
Text("增加")
}
Button(
onClick = { viewModel.reset() },
modifier = Modifier.padding(top = 8.dp)
) {
Text("重置")
}
}
}
进阶用法
1. 处理复杂状态
class UserViewModel : ViewModel() {
// 使用密封类管理多种状态
private val _userState = MutableStateFlow<UserState>(UserState.Loading)
val userState: StateFlow<UserState> = _userState.asStateFlow()
init {
loadUserData()
}
private fun loadUserData() {
viewModelScope.launch {
_userState.value = UserState.Loading
try {
val user = userRepository.getUser()
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message ?: "未知错误")
}
}
}
fun retry() {
loadUserData()
}
}
sealed class UserState {
object Loading : UserState()
data class Success(val user: User) : UserState()
data class Error(val message: String) : UserState()
}
2. 在 Compose 中处理复杂状态
@Composable
fun UserProfileScreen() {
val viewModel: UserViewModel = viewModel()
val userState by viewModel.userState.collectAsState()
when (val state = userState) {
is UserState.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is UserState.Success -> {
UserDetailsView(user = state.user)
}
is UserState.Error -> {
ErrorView(
message = state.message,
onRetry = { viewModel.retry() }
)
}
}
}
3. 使用 Hilt 依赖注入
// 添加 Hilt 依赖
implementation 'com.google.dagger:hilt-android:2.48.1'
kapt 'com.google.dagger:hilt-compiler:2.48.1'
implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
// 使用 @HiltViewModel 注解
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val settingsRepository: SettingsRepository
) : ViewModel() {
private val _theme = MutableStateFlow(AppTheme.SYSTEM)
val theme: StateFlow<AppTheme> = _theme.asStateFlow()
init {
loadSettings()
}
private fun loadSettings() {
viewModelScope.launch {
_theme.value = settingsRepository.getTheme()
}
}
fun setTheme(theme: AppTheme) {
viewModelScope.launch {
settingsRepository.saveTheme(theme)
_theme.value = theme
}
}
}
// 在 Compose 中使用
@Composable
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
val theme by viewModel.theme.collectAsState()
// UI 实现...
}
最佳实践
1. 状态管理原则
原则 | 说明 |
---|---|
单一数据源 | ViewModel 应是 UI 状态的唯一真实来源 |
单向数据流 | UI 事件 → ViewModel → 更新状态 → UI 刷新 |
状态最小化 | 只暴露 UI 需要的最小状态 |
不可变性 | 对外暴露的状态应是不可变的(使用 StateFlow 或 LiveData) |
2. 状态提升模式
@Composable
fun UserInputSection(
username: String,
onUsernameChange: (String) -> Unit,
password: String,
onPasswordChange: (String) -> Unit,
onLogin: () -> Unit
) {
Column {
OutlinedTextField(
value = username,
onValueChange = onUsernameChange,
label = { Text("用户名") }
)
OutlinedTextField(
value = password,
onValueChange = onPasswordChange,
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation()
)
Button(onClick = onLogin) {
Text("登录")
}
}
}
@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
val state by viewModel.uiState.collectAsState()
UserInputSection(
username = state.username,
onUsernameChange = viewModel::updateUsername,
password = state.password,
onPasswordChange = viewModel::updatePassword,
onLogin = viewModel::login
)
if (state.isLoading) {
CircularProgressIndicator()
}
}
3. 处理副作用
@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
val state by viewModel.uiState.collectAsState()
// 处理导航副作用
val navController = rememberNavController()
LaunchedEffect(state.isLoginSuccess) {
if (state.isLoginSuccess) {
navController.navigate("home")
}
}
// 处理错误提示
val context = LocalContext.current
LaunchedEffect(state.errorMessage) {
state.errorMessage?.let {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
viewModel.errorShown()
}
}
// UI 实现...
}
4. ViewModel 生命周期管理
@Composable
fun MyApp() {
val navController = rememberNavController()
val backstackEntry by navController.currentBackStackEntryAsState()
// 获取当前路由的 ViewModel
val currentRoute = backstackEntry?.destination?.route
val viewModelStoreOwner = backstackEntry ?: LocalViewModelStoreOwner.current
Scaffold { innerPadding ->
NavHost(
navController = navController,
startDestination = "home",
modifier = Modifier.padding(innerPadding)
) {
composable("home") {
// HomeScreen 使用当前导航条目的 ViewModel
val homeViewModel: HomeViewModel = viewModel(viewModelStoreOwner)
HomeScreen(viewModel = homeViewModel)
}
composable("profile") {
ProfileScreen()
}
}
}
}
常见问题解决方案
1. 状态持久化
class CounterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
companion object {
private const val COUNT_KEY = "count"
}
private val _count = MutableStateFlow(
savedStateHandle.get<Int>(COUNT_KEY) ?: 0
)
val count: StateFlow<Int> = _count.asStateFlow()
init {
// 保存状态到 SavedStateHandle
viewModelScope.launch {
_count.collect { count ->
savedStateHandle[COUNT_KEY] = count
}
}
}
fun increment() {
_count.value += 1
}
}
2. 性能优化
class ProductViewModel : ViewModel() {
// 使用 derivedStateOf 优化派生状态
private val _products = MutableStateFlow(emptyList<Product>())
val products: StateFlow<List<Product>> = _products.asStateFlow()
val favoriteProducts: StateFlow<List<Product>> =
_products.derivedStateFlow {
products.value.filter { it.isFavorite }
}
// 使用 flatMapLatest 处理异步操作
val searchQuery = MutableStateFlow("")
val searchResults = searchQuery
.debounce(300) // 防抖 300ms
.flatMapLatest { query ->
if (query.isEmpty()) {
flowOf(emptyList())
} else {
productRepository.searchProducts(query)
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
}
3. 测试策略
class CounterViewModelTest {
@Test
fun `increment should increase count by 1`() = runTest {
// 创建 ViewModel
val viewModel = CounterViewModel()
// 初始状态验证
assertEquals(0, viewModel.count.value)
// 执行操作
viewModel.increment()
// 验证结果
assertEquals(1, viewModel.count.value)
}
}
@Composable
fun CounterPreview() {
// 预览模式使用假数据
val fakeViewModel = CounterViewModel().apply {
_count.value = 5
}
CounterScreen(viewModel = fakeViewModel)
}
总结
Jetpack Compose 与 ViewModel 的结合提供了强大的状态管理能力:
ViewModel 作为状态容器:管理 UI 状态和业务逻辑
响应式状态更新:使用 StateFlow/LiveData + collectAsState 实现自动刷新
单向数据流:确保状态变化的可预测性和可维护性
生命周期感知:自动处理配置更改和资源清理
依赖注入支持:通过 Hilt 简化依赖管理
遵循这些模式和实践,你可以构建出结构清晰、可维护且高效响应的 Compose 应用。