前言 使用 ViewModel我们可以方便管理模型数据的生命周期。
使用方式参考
https://www.psvmc.cn/article/2025-08-12-jetpack-compose-comp-viewmodel.html
ViewModel 与 Compose 的数据交互本质是:
ViewModel 用可观察容器(StateFlow/LiveData)持有数据 → Compose 通过 collectAsState/observeAsState 观察状态 → 数据变化时触发 UI 重组 。
ViewModel中的数据怎样供Compose使用呢?
两种实现方式 使用MutableState 优点是使用起来简单,但是从架构设计 上来说是不太合理的。
使用没有问题,并不是不能用,只是不推荐使用。
ViewModel 中使用 mutableStateOf:
ViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 import androidx.lifecycle.ViewModelimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.getValueimport androidx.compose.runtime.setValueclass MyViewModel : ViewModel () { var count by mutableStateOf(0 ) private set fun increment () { count++ } }
Composable
1 2 3 4 5 6 7 8 @Composable fun MyScreen (viewModel: MyViewModel = viewModel() ) { Text("Count: ${viewModel.count} " ) Button(onClick = { viewModel.increment() }) { Text("Increment" ) } }
这种方式可以使用,并不会引起内存泄露。
Compose 会自动跟踪哪些 UI 元素依赖了 State 对象。当 State 的值发生变化时,Compose 只会重组依赖它的那部分 UI,而不是整个屏幕。
更重要的是,当一个 Composable 函数不再被使用(例如,用户导航到其他页面),Compose 会自动解除它与所有 State 对象的关联,从而避免了因 State 引用导致的内存泄漏。
使用StateFlow 推荐的方式
ViewModel 中使用 StateFlow:
基本数据 ViewModel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.launchclass MyViewModel : ViewModel () { private val _count = MutableStateFlow(0 ) val count: StateFlow<Int > = _count fun increment () { _count.value++ } fun fetchData () { viewModelScope.launch { val result = someExpensiveOperation() _count.value = result } } }
Composable 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import androidx.compose.runtime.getValueimport androidx.lifecycle.compose.collectAsStateWithLifecycle@Composable fun MyScreen (viewModel: MyViewModel = viewModel() ) { val count by viewModel.count.collectAsStateWithLifecycle() Text("Count: $count " ) Button(onClick = { viewModel.increment() }) { Text("Increment" ) } Button(onClick = { viewModel.fetchData() }) { Text("Fetch Data" ) } }
列表数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 data class Todo (val id: Int , val title: String, val isCompleted: Boolean = false )import androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.launchclass TodoViewModel : ViewModel () { private val _todos = MutableStateFlow<List<Todo>>(emptyList()) val todos: StateFlow<List<Todo>> = _todos.asStateFlow() init { loadInitialData() } private fun loadInitialData () { viewModelScope.launch { kotlinx.coroutines.delay(1000 ) val initialTodos = listOf( Todo(1 , "学习 Jetpack Compose" ), Todo(2 , "掌握 StateFlow" ), Todo(3 , "写一个示例 App" ) ) _todos.value = initialTodos } } fun addTodo (title: String ) { val newTodo = Todo( id = (_todos.value.maxByOrNull { it.id }?.id ?: 0 ) + 1 , title = title ) _todos.value = _todos.value + newTodo } fun toggleTodoCompletion (id: Int ) { _todos.value = _todos.value.map { todo -> if (todo.id == id) { todo.copy(isCompleted = !todo.isCompleted) } else { todo } } } fun deleteTodo (id: Int ) { _todos.value = _todos.value.filterNot { it.id == id } } }
总结 在 ViewModel 中直接使用 mutableStateOf 对于极其简单的场景可能看起来很方便,但它带来了架构耦合 和功能受限 的问题。
最佳实践是:
ViewModel 应该暴露 StateFlow :这符合分层架构原则,使 ViewModel 更加健壮、可测试和可复用。
Composable 应该观察 StateFlow :使用 collectAsStateWithLifecycle 可以确保状态收集是生命周期安全的,避免内存泄漏和不必要的资源消耗。
因此,在 ViewModel 中直接使用 mutableStateOf 通常是不合理的 ,推荐使用 StateFlow 作为 ViewModel 向 UI 暴露状态的首选方式。
最佳实现 最佳的方式是使用StateFlow结合自定义状态类。但是使用起来相对也麻烦点。
定义状态类 1 2 3 4 5 6 7 8 9 data class UserUiState ( val isLoading: Boolean = false , val user: User? = null , val errorMsg: String? = null ) data class User (val name: String, val age: Int )
ViewModel ViewModel 管理复合状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class UserViewModel : ViewModel () { private val _uiState = MutableStateFlow(UserUiState(isLoading = true )) val uiState = _uiState.asStateFlow() fun loadUser () { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true ) try { delay(1500 ) val mockUser = User("张三" , 28 ) _uiState.value = UserUiState(user = mockUser) } catch (e: Exception) { _uiState.value = UserUiState(errorMsg = "加载失败:${e.message} " ) } } } }
Compose Compose 根据状态展示 UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Composable fun UserScreen () { val vm: UserViewModel = viewModels() val uiState = vm.uiState.collectAsState(initial = UserUiState()).value when { uiState.isLoading -> Text("加载中..." ) uiState.errorMsg != null -> Text("错误:${uiState.errorMsg} " ) uiState.user != null -> Text("用户:${uiState.user.name} ,年龄:${uiState.user.age} " ) } Text( text = "点击加载用户" , modifier = Modifier.clickable { viewModel.loadUser() } ) }
常见问题 为什么适合用一个数据类封装状态? 符合 “单一可信源” 原则
页面所有状态(加载中、数据、错误、用户输入等)集中在一个数据类中,避免状态分散在 ViewModel 的多个变量中(如 isLoading: Boolean、data: List<Item>、error: String? 分别定义),确保状态间的一致性。 例如:加载中 和 数据就绪 是互斥状态,用一个类封装可通过逻辑保证不会同时为 true:
1 2 3 4 5 6 7 8 9 10 data class PageUiState ( val isLoading: Boolean = false , val data : List<String>? = null , val error: String? = null ) _uiState.value = PageUiState(isLoading = true ) _uiState.value = PageUiState(data = listOf("A" , "B" ))
简化状态传递与观察
Compose 中只需观察一个状态对象(如 uiState.collectAsState()),而非多个独立状态(isLoading.collectAsState()、data.collectAsState() 等),减少模板代码。 对比两种方式:
1 2 3 4 5 6 7 8 val isLoading by viewModel.isLoading.collectAsState()val data by viewModel.data .collectAsState()val error by viewModel.error.collectAsState()val uiState by viewModel.uiState.collectAsState()
便于状态回溯与调试
每个状态变更都是一个完整的数据类实例,可通过日志记录完整状态快照(如 Log.d(“State”, “Updated: $uiState”)),轻松追踪状态流转过程。
适配 Compose 重组特性
Compose 会根据状态变化智能重组,单一状态类的属性变更只会触发依赖该属性的 UI 部分重组(而非整个页面),性能不受影响。
对象变化,对象中未改变的值会触发组件重组吗? 在 Jetpack Compose 中,状态类中部分值变化时,未变化的字段不会触发依赖该字段的 UI 部分重新渲染 。
Compose 会通过 “智能重组” 机制,仅更新真正依赖变化数据的 UI 组件,其他部分保持不变。
ViewModel中为什么MutableStateFlow还要转成StateFlow? MutableStateFlow 提供了 value 的 setter 方法(可以直接修改值),而通过 asStateFlow() 转换为 StateFlow 后,得到的是一个只读视图 。 外部只能观察它的值(通过 collect),但不能直接修改它,确保状态只能在合适的范围内被修改(通常是创建它的类内部)。
通过暴露 StateFlow 而非 MutableStateFlow,向调用者清晰传达了 这是一个状态数据流,你应该观察它而不是修改它 的意图,让代码职责更明确。
为什么ViewModel 中直接使用 Compose 状态不合理? 在 ViewModel 中直接使用 Compose 状态(如 mutableStateOf)不合理 。
主要违反了架构设计原则和分层职责,具体原因如下:
职责边界模糊
ViewModel 的核心职责是:
持有与 UI 相关的数据,且生命周期长于 UI(不受配置变更影响)
处理业务逻辑,暴露数据给 UI 层
不依赖 UI 框架(如 Compose、Activity 等)
而 Compose 状态(mutableStateOf 等)是 UI 层的状态持有工具 ,其设计目的是为了驱动 Composable 重组,属于 UI 框架的一部分。 将 Compose 状态放入 ViewModel,会导致 ViewModel 依赖 UI 框架,违反了 “ViewModel 应与 UI 框架解耦” 的设计原则。
生命周期不匹配
Compose 状态的生命周期与 Composable 组件绑定,会随组件的创建 / 销毁而变化。
ViewModel 的生命周期与 Activity/Fragment 一致。
架构扩展性问题
若未来 UI 层不使用 Compose(如迁移到其他框架),ViewModel 中的 Compose 状态会成为强耦合点,增加重构成本。
Compose 状态的设计初衷是局部 UI 状态管理,不适合作为跨层(数据层→UI 层)的数据传递载体,缺乏 StateFlow 等数据流的背压处理、线程安全等特性。
ViewModel 的核心是 “管理数据,解耦 UI”,而 Compose 状态是 “驱动 UI 重组的工具”。二者属于不同层级,强行在 ViewModel 中使用 Compose 状态会破坏架构分层,导致维护性和扩展性问题。 正确的模式是:ViewModel 用 StateFlow 管理状态,UI 层通过 collectAsState() 桥接为 Compose 状态,清晰分离职责。
代码组织方式
方式
优点
缺点
适用场景
分层架构
职责清晰,耦合度低,可测试性高,易于扩展
对于非常小的项目可能显得有些繁琐
中大型项目 ,需要清晰架构和长期维护的项目
功能模块
内聚性高,查找方便
模糊了分层界限,可能导致 ViewModel 依赖 UI
小型项目 或团队偏好按功能模块组织代码