场景
kotlin
class Link(val key: String) {
enum class Phase { OFF, ON, RETRY }
var phase: Phase = Phase.OFF
}
var active: Link? = null
val revision = MutableStateFlow(0L) // 连接层 state 变了就 revision++
fun onPhaseChanged() {
revision.value = System.currentTimeMillis()
}
fun onLinkLost() {
active?.phase = Link.Phase.RETRY // 不换 Link 实例,只改内部 phase
onPhaseChanged()
}
onLinkLost() 只改 active!!.phase = RETRY,不换新 Link 实例。
问题写法:distinct 后面接 stateIn
kotlin
val linkFlow: StateFlow<Link?> = revision
.map { active }
.distinctUntilChangedBy { "${it?.key}-${it?.phase}" }
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
phase 从 ON 变 RETRY 时:
| 步骤 | 发生什么 |
|---|---|
revision emit |
触发整条链 |
distinctUntilChangedBy |
key 从 k-ON 变为 k-RETRY,放行 |
stateIn 写 .value |
新值仍是同一个 active 引用 → old == new → 不通知 collect |
distinct 按逻辑 key 去重;StateFlow 按引用 去重------多挡一层。
kotlin
linkFlow.collect { link ->
showOnline(link?.phase == Link.Phase.ON)
}
collect 不重跑时,showOnline 不会更新;link?.phase 虽是 getter,lambda 根本没执行。
.value 与缓存是同一规则:
kotlin
val sameRef = linkFlow.value // 引用在
// phase 已 RETRY,但 StateFlow 不会为此再推一次
替代:shareIn(replay = 1)
kotlin
val linkFlow: SharedFlow<Link?> = revision
.map { active }
.distinctUntilChangedBy { "${it?.key}-${it?.phase}" }
.shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
distinct 放行后,每次 向 SharedFlow emit 一次,不因引用相同而跳过 ;活跃 collect 会再跑。
| stateIn | shareIn(replay = 1) | |
|---|---|---|
同引用、仅 phase 变 |
常不通知 | 通知 |
| 晚订阅首包 | .value |
replay 最后一次 emit |
| 同步读 | .value |
replayCache.lastOrNull() 或读 active?.phase |
replay = 0 时晚订阅要等下一次 revision,进界面可能一直没有首包。
实践注意、边界与自检
- 必须
StateFlow+.value且实例可变:stateIn前改成不可变快照(如data class Snap(val key: String?, val phase: Phase)),不要直接缓存Link?。 - 求简单、同引用
phase也要推:distinctUntilChangedBy+shareIn(replay = 1)。 - 同步读当前
phase:直接读active?.phase,别假设热流一定会再 emit。 - 只改
active却不revision++:distinct 上游无 tick,换 shareIn 也没用。 - 订阅方
if (last === link) return:自行挡刷新,与算子无关。
自检:distinct 日志/key 已变而 UI 不动 → 查 stateIn 引用判等;晚订阅无首包 → 查 replay;要同步态 → 读变量或快照,别只盯流。