关于 android-viewmodel
面向 Android Kotlin 开发者的 ViewModel 与状态管理指南,涵盖 StateFlow、SharedFlow 及生命周期感知的收集方式,并强调不可变 UI 状态和显式 backing fields。
主要功能
- 01用 StateFlow 表示持久 UI 状态
- 02用 SharedFlow 发送一次性事件
- 03生命周期感知地收集流
- 04在 viewModelScope 中启动任务
- 05优先使用显式 backing fields
适用场景
- 01设计单一屏幕的不可变状态
- 02处理导航和 Snackbar 等 UI 事件
- 03在 Compose 中收集状态与事件
- 04在 View 系统中按生命周期收集流
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ new-silvermoon/ awesome-android-agent-skills --skill android-viewmodel安装后在 Claude Code 中输入/android-viewmodel确认已加载。
详细介绍
Android ViewModel 和状态管理
说明
使用 ViewModel 管理 UI 状态和业务逻辑。它应在配置变更后继续存在,并公开不可变 API。
Kotlin 2.3+
优先使用显式后备字段,而不是传统的
_state/state后备属性模式。
- Kotlin 2.3:实验性功能(
-Xexplicit-backing-fields)- Kotlin 2.4+:稳定(推荐)
除非目标是较旧的 Kotlin 版本,否则避免创建重复的后备属性。
1. UI 状态(StateFlow)
用途
表示持久的 UI 状态。
示例:
- 加载中
- 成功(data)
- 错误(message)
- 空
声明
使用显式后备字段。
val uiState: StateFlow<UiState>
field = MutableStateFlow(UiState.Loading)
后备字段是可变的(MutableStateFlow),而使用方只能看到 StateFlow。
更新状态
优先使用 update { } 进行原子更新。
_uiState.update { old ->
old.copy(isLoading = false)
}
使用显式后备字段时:
uiState.update {
it.copy(isLoading = false)
}
在适当的情况下,也可以直接赋值。
uiState.value = UiState.Success(data)
2. 单次事件(SharedFlow)
用途
用于不会在重组或配置变更后保留的临时事件。
示例:
- 导航
- 显示 Snackbar
- 显示 Toast
- 打开对话框
- 请求权限
声明
val uiEvent: SharedFlow<UiEvent>
field = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 1
)
replay = 0 可确保重新创建后不会重放事件。
对于使用 tryEmit() 发送的 UI 事件,建议设置 extraBufferCapacity = 1。
发送事件
挂起:
uiEvent.emit(UiEvent.NavigateBack)
非挂起:
uiEvent.tryEmit(UiEvent.ShowSnackbar("Saved"))
3. 在 Compose 中收集
StateFlow
始终以感知生命周期的方式收集。
val state by viewModel.uiState.collectAsStateWithLifecycle()
SharedFlow
在单个 LaunchedEffect 中收集。
LaunchedEffect(Unit) {
viewModel.uiEvent.collect { event ->
when (event) {
is UiEvent.NavigateBack -> { /* ... */ }
is UiEvent.ShowSnackbar -> { /* ... */ }
}
}
}
不要使用 collectAsState() 收集 SharedFlow。
4. 在 XML/View 系统中收集
使用感知生命周期的收集方式。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
render(it)
}
}
}
事件也同样如此。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiEvent.collect(::handleEvent)
}
}
5. 协程作用域
在 viewModelScope 中启动所有 ViewModel 工作。
viewModelScope.launch {
repository.refresh()
}
业务逻辑最好委托给 UseCase 或 Repository。
6. 状态设计
使用单个不可变状态对象表示整个屏幕。
data class UiState(
val isLoading: Boolean = false,
val items: List<Item> = emptyList(),
val error: String? = null
)
除非多个 StateFlow 的生命周期确实不同,否则避免为同一个屏幕公开多个互不相关的 StateFlow。
7. 最佳实践
公开不可变 API(StateFlow、SharedFlow)
优先使用显式后备字段(Kotlin 2.4+)
使用不可变 UI 状态
修改状态时使用 update {}
使用 SharedFlow 保存单次事件
不要在 Composable 中编写业务逻辑
使用 collectAsStateWithLifecycle()
在 View 中使用 repeatOnLifecycle()
在 viewModelScope 中启动工作
不要公开 MutableStateFlow
不要使用 StateFlow 处理导航事件
不要使用 SharedFlow 保存持久的屏幕状态
显式后备字段可用时,不要保留重复的 _state 属性