移动/游戏
Android ViewModel…
适用于 Android/Kotlin 项目中使用 ViewModel 管理 UI 状态、一次性事件,并在 Compose 或 XML/View 中生命周期感知收集的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
Android ViewModel 状态特定
解决思路
- 使用 `ViewModel` 持有 UI 状态和业务逻辑,使其在配置变更后存活,并对外暴露不可变 API。
- 在 Kotlin 2.3+ 优先使用 Explicit Backing Fields,替代传统的 `_state`/`state` backing property 模式。
- Kotlin 2.3 中 Explicit Backing Fields 需启用实验特性:`-Xexplicit-backing-fields`;Kotlin 2.4+ 已稳定,推荐使用。
- 仅在面向旧版 Kotlin 时创建重复 backing property;否则不要创建重复的 `_state` 属性。
- 用 `StateFlow` 表示持久 UI 状态,例如 `Loading`、`Success(data)`、`Error(message)`、`Empty`。
- 声明 UI 状态时使用显式 backing field:`val uiState: StateFlow<UiState> field = MutableStateFlow(UiState.Loading)`。
- 让 backing field 保持可变(`MutableStateFlow`),而消费方只看到 `StateFlow`。
- 更新状态时优先使用 `update { }` 进行原子更新:`_uiState.update { old -> old.copy(isLoading = false) }`。
- 使用显式 backing fields 时通过 `uiState.update { it.copy(isLoading = false) }` 更新状态。
- 合适时也可直接赋值:`uiState.value = UiState.Success(data)`。
- 用 `SharedFlow` 表示不应在重组或配置变更后存活的一次性事件,例如导航、显示 Snackbar、显示 Toast、打开 Dialog、请求权限。
- 声明一次性事件时使用显式 backing field:`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"))`。
- 在 Compose 中收集 `StateFlow` 时始终生命周期感知:`val state by viewModel.uiState.collectAsStateWithLifecycle()`。
- 在 Compose 中收集 `SharedFlow` 时放在单个 `LaunchedEffect` 内,并用 `when` 处理事件类型。
- 不要用 `collectAsState()` 收集 `SharedFlow`。
- 在 XML/View System 中收集 `StateFlow` 时使用生命周期感知收集:`lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { render(it) } } }`。
- 在 XML/View System 中收集事件时同样使用 `repeatOnLifecycle(Lifecycle.State.STARTED)`:`viewModel.uiEvent.collect(::handleEvent)`。
- 所有 ViewModel 工作都应在 `viewModelScope` 中启动:`viewModelScope.launch { repository.refresh() }`。
- 业务逻辑最好委托给 UseCases 或 Repositories。
- 用一个不可变状态对象表示整个屏幕,例如 `data class UiState(val isLoading: Boolean = false, val items: List<Item> = emptyList(), val error: String? = null)`。
- 避免为一个屏幕暴露多个不相关的 `StateFlow`,除非它们确实有不同生命周期。
- 对外暴露不可变 API:`StateFlow`、`SharedFlow`。
- 使用不可变 UI 状态。
- 修改状态时使用 `update {}`。
- 将一次性事件保存在 `SharedFlow` 中。
- 不要把业务逻辑放进 Composables。
- 使用 `collectAsStateWithLifecycle()`。
- 在 View 中使用 `repeatOnLifecycle()`。
- 在 `viewModelScope` 中启动工作。
- 不要暴露 `MutableStateFlow`。
- 不要用 `StateFlow` 处理导航事件。
- 不要用 `SharedFlow` 处理持久屏幕状态。
- 当 Explicit Backing Fields 可用时,不要保留重复的 `_state` 属性。
适用边界 · 注意事项
- 目标 Kotlin 版本低于 2.3,或无法启用 Explicit Backing Fields 时,继续使用传统 `_state`/`state` backing property 模式。
- 不要将 `SharedFlow` 用于持久屏幕状态;持久屏幕状态应使用 `StateFlow`。
- 不要将 `StateFlow` 用于一次性导航/提示事件;这些事件应使用 `SharedFlow`。