Appearance
one-shot-event-demo
1. 实验目标
验证“提交成功 Toast / 导航 / 支付回调”这类一次性事件,为什么不能直接塞进持久 uiState:
uiState只承载可重绘状态:isSubmitting、submitCount、lastResultMutableSharedFlow单独承载一次性事件:Toast / 导航 / 支付结果repeatOnLifecycle保证页面回到前台时重新订阅,但不会平白重放旧事件
2. 对应事故样板
- 为什么成功 Toast 会在旋转屏幕后重复弹?
- 一句话答:因为把一次性事件升级成了可恢复 state,页面重建后又按“最新状态”重新消费了一次。
- 为什么导航结果不该塞进
SavedStateHandle?- 一句话答:导航结果是脉冲,不是事实;恢复时应重建页面状态,而不是重放旧事件。
3. 运行方式
bash
cd labs/android/host
./gradlew installDebug打开 Android Host → 打开 one-shot event 实验室。
4. 观察步骤
- 点击“模拟提交成功”
- 观察页面状态区:
submitCount与lastResult更新 - 观察 Toast:只弹一次,并在事件日志区留下最近一次消息
- 旋转屏幕或退后台再回来:页面状态保留,但旧 Toast 不会再次消费
5. 关键实现速览
5.1 State 与 Event 分通道
kotlin
private val _uiState = MutableStateFlow(OneShotUiState())
private val _events = MutableSharedFlow<UiEvent>()这段代码说明:State 是“当前事实”,Event 是“一次性脉冲”,通道必须拆开。
完整源码:OneShotEventDemoActivity.kt
5.2 事件只在收集时消费一次
kotlin
viewModelScope.launch {
_events.emit(UiEvent.ShowToast("提交成功,只消费一次 #$nextCount"))
}这段代码说明:一次性事件应由事件流主动发射,而不是靠重绘时读取旧字段触发副作用。
完整源码:OneShotEventDemoActivity.kt
6. 对应知识库文档
- 理论主文档:Jetpack Compose 状态模型、StateFlow 与重组优化
- 状态分层总览:状态管理、路由、架构