前言
使用 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 | import androidx.lifecycle.ViewModel |
Composable
1 |
|
这种方式可以使用,并不会引起内存泄露。
- Compose 会自动跟踪哪些 UI 元素依赖了
State对象。当State的值发生变化时,Compose 只会重组依赖它的那部分 UI,而不是整个屏幕。 - 更重要的是,当一个 Composable 函数不再被使用(例如,用户导航到其他页面),Compose 会自动解除它与所有
State对象的关联,从而避免了因State引用导致的内存泄漏。
使用StateFlow
推荐的方式
ViewModel 中使用 StateFlow:
基本数据
ViewModel
1 | import androidx.lifecycle.ViewModel |
Composable
1 | import androidx.compose.runtime.getValue |
列表数据
1 | // 数据类 |
总结
在 ViewModel 中直接使用 mutableStateOf 对于极其简单的场景可能看起来很方便,但它带来了架构耦合和功能受限的问题。
最佳实践是:
- ViewModel 应该暴露
StateFlow:这符合分层架构原则,使 ViewModel 更加健壮、可测试和可复用。 - Composable 应该观察
StateFlow:使用collectAsStateWithLifecycle可以确保状态收集是生命周期安全的,避免内存泄漏和不必要的资源消耗。
因此,在 ViewModel 中直接使用 mutableStateOf 通常是不合理的,推荐使用 StateFlow 作为 ViewModel 向 UI 暴露状态的首选方式。
最佳实现
最佳的方式是使用StateFlow结合自定义状态类。但是使用起来相对也麻烦点。
定义状态类
1 | // 封装页面所需的所有状态 |
ViewModel
ViewModel 管理复合状态
1 | class UserViewModel : ViewModel() { |
Compose
Compose 根据状态展示 UI
1 |
|
常见问题
为什么适合用一个数据类封装状态?
符合 “单一可信源” 原则
页面所有状态(加载中、数据、错误、用户输入等)集中在一个数据类中,避免状态分散在 ViewModel 的多个变量中(如 isLoading: Boolean、data: List<Item>、error: String? 分别定义),确保状态间的一致性。
例如:加载中 和 数据就绪 是互斥状态,用一个类封装可通过逻辑保证不会同时为 true:
1 | // 集中管理的状态类 |
简化状态传递与观察
Compose 中只需观察一个状态对象(如 uiState.collectAsState()),而非多个独立状态(isLoading.collectAsState()、data.collectAsState() 等),减少模板代码。
对比两种方式:
1 | // 方式1:分散状态(繁琐) |
便于状态回溯与调试
每个状态变更都是一个完整的数据类实例,可通过日志记录完整状态快照(如 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 | 小型项目或团队偏好按功能模块组织代码 |