Jetpack Compose 与 ViewModel 的完美结合
在 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.Loadingtry {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.currentLaunchedEffect(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()// 获取当前路由的 ViewModelval currentRoute = backstackEntry?.destination?.routeval viewModelStoreOwner = backstackEntry ?: LocalViewModelStoreOwner.currentScaffold { innerPadding ->NavHost(navController = navController,startDestination = "home",modifier = Modifier.padding(innerPadding)) {composable("home") {// HomeScreen 使用当前导航条目的 ViewModelval 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 {// 保存状态到 SavedStateHandleviewModelScope.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 {@Testfun `increment should increase count by 1`() = runTest {// 创建 ViewModelval 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 应用。