snapshotFlow 把 Snapshot 状态系统桥接到 Kotlin Flow,让你能用 Flow 操作符处理 Compose 状态变化。produceState把 LiveData 拉进 Compose,snapshotFlow 把 Compose State 推出去给 Flow。 作用:snapshotFlow 不受compose重组机制影响,值变了才读取state值并且emit,转为 Flow流处理。
例子 搜索防抖
kotlin
// 场景1:监听 Compose State 变化做异步操作
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
snapshotFlow { query } // 监听 query 的变化
.debounce(300) // 防抖 300ms
.filter { it.length > 2 } // 至少3个字符
.mapLatest { api.search(it) } // 自动取消上次请求
.collect { results ->it }
}
}
// 场景2:将 Snapshot State 转为 StateFlow
val state = MutableStateFlow(0)
LaunchedEffect(Unit) {
snapshotFlow { scrollState.value }
.collect { state.value = it }
}
多状态组合 → 单一 Flow
scss
@Composable
fun FormScreen() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var age by remember { mutableStateOf("") }
var isFormValid by remember { mutableStateOf(false) } //这里可以使用derivedStateOf
LaunchedEffect(Unit) {
snapshotFlow {
name.isNotBlank() && email.contains("@") && (age.toIntOrNull() ?: 0) > 0
}.collect { isFormValid = it }
}
Column {
TextField(value = name, onValueChange = { name = it })
TextField(value = email, onValueChange = { email = it })
TextField(value = age, onValueChange = { age = it })
Button(enabled = isFormValid, onClick = { /* submit */ }) {
Text("提交")
}
}
}
滚动位置监听
scss
@Composable
fun CollapsingToolbar() {
val scrollState = rememberScrollState()
var isCollapsed by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
snapshotFlow { scrollState.value > 200 }
.distinctUntilChanged()
.collect { isCollapsed = it }
}
Box {
LazyColumn(state = scrollState) { /* ... */ }
TopAppBar(
modifier = Modifier.animateContentSize(),
title = if (isCollapsed) Text("简洁模式") else Text("完整标题")
)
}
}
写入 SharedPreferences / DataStore
kotlin
@Composable
fun ThemeToggle(dataStore: DataStore<Preferences>) {
var isDark by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
snapshotFlow { isDark }
.drop(1) // 跳过初始值,避免启动时就写入
.collect { dark ->
dataStore.edit { prefs ->
prefs[THEME_KEY] = dark
}
}
}
Switch(checked = isDark, onCheckedChange = { isDark = it })
}
地图中心点跟随 + 节流(GPS 上报类场景)
kotlin
@Composable
fun MapScreen() {
var cameraPosition by remember { mutableStateOf(CameraPosition(0.0, 0.0, 12f)) }
LaunchedEffect(Unit) {
snapshotFlow { cameraPosition }
.sample(1000) // 每秒最多 emit 一次
.collect { pos ->
analytics.track("map_move", "lat" to pos.lat, "lng" to pos.lng)
}
}
}
原理:
kotlin
fun <T> snapshotFlow(block: () -> T): Flow<T> = flow {
val manager = SnapshotFlowManager()
val needToRerunBlock = Channel<Unit>(1)
try {
var lastValue = manager.runAndWatch(needToRerunBlock, block)
emit(lastValue) // ① 首值
while (true) {
needToRerunBlock.receive() // ② 等待变化信号
val newValue = manager.runAndWatch(needToRerunBlock, block)
if (newValue != lastValue) { // ③ 去重
lastValue = newValue
emit(newValue)
}
}
} finally {
manager.reportSnapshotFlowCancellation(...) // ④ 取消时清理
manager.dispose()
}
}
四步循环:执行 block → 记录读取的 state → 等待 ApplyObserver 信号 → 值变化则 emit → 循环。协程取消时 receive() 抛 CancellationException,finally 清理订阅。
runAndWatch --- 如何"记录读取"
kotlin
// ★ 字段初始化时注册,Manager 创建即注册
private val unregisterApplyObserver =
Snapshot.registerApplyObserver { changed, _ ->
var toNotify: SendChannel<Unit>? = null
synchronized(lock) {
val watchSet = watchSet
if (watchSet == null) {
// 只观察了一个对象,直接 contains
if (changed.contains(soleWatchedObject)) {
toNotify = subscribedChannel // ★ needToRerunBlock 赋值
}
} else {
// 观察了多个对象,遍历检查
if (watchSet.any { changed.contains(it) }) {
toNotify = subscribedChannel
}
}
}
toNotify?.trySend(Unit) // ★ 命中 → 发信号
}
override fun dispose() {
unregisterApplyObserver.dispose() // ★ 销毁时注销
// ...
}
internal fun <T> runAndWatch(channel: SendChannel<Unit>, block: () -> T): T {
val result = Snapshot.takeSnapshot(readObserverFor(channel)) // 创建可读快照
.run {
clearWatchSet(channel) // 清空上一次的观察列表
try {
enter(block) // 在快照中执行 block
} finally {
dispose()
}
}
commitSubscriptionChanges() // 将新观察列表提交生效
return result
}
关键在于 Snapshot.takeSnapshot(readObserver). 它创建了一个"可追踪读取"的快照------block 中每读取一个 mutableStateOf 对象,都会回调 readObserver,从而在 Manager 中记录 "这个 state 对象被订阅了"。