jetpack compose 副作用 snapshotFlow

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 对象被订阅了"。

相关推荐
三少爷的鞋6 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)10 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码12 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程12 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓17 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer