Jetpack Compose - 值状态变化监听

前言

我常用的监听值变化有两种方式

  • 使用 LaunchedEffect

监听变化

在 Jetpack Compose 里,有多种方式可以监听 MutableState 的变化。

下面为你详细介绍不同的实现方法:

ViewModel中监听(推荐)

如果需要在非 Composable 代码(如 ViewModel)中监听 MutableState 变化,

同样可以通过 snapshotFlow 转换为 Flow,再在 ViewModel 中收集。

监听单个值

示例:在 ViewModel 中监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class MyViewModel : ViewModel() {
// 定义一个 MutableState(可在 Composable 中修改)
private val mName = mutableStateOf("")

init {
// 监听 name 的变化
viewModelScope.launch {
snapshotFlow { mName.value }.collect { newName ->
println("ViewModel 中监听到 name 变化:$newName")
// 执行业务逻辑(如校验、缓存等)
}
}
}

// 供外部(如 Composable)调用修改状态的方法
fun updateName(newName: String) {
mName.value = newName
}
}

使用场景
当需要在业务层(ViewModel)响应 UI 层的状态变化时(如输入校验、实时搜索等),这种方式非常合适。

监听多个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
init {   
viewModelScope.launch {
// 使用 combine 操作符合并 mName 和 mAge 的 Flow(支持 2+ 个 Flow)
combine(
snapshotFlow { mName.value }, // 第一个被监听的值:姓名的 Flow
snapshotFlow { mAge.value } // 第二个被监听的值:年龄的 Flow
// 若需监听更多值,可继续在后面添加(如:snapshotFlow { mGender.value })
)
{ newName, newAge ->
// 合并后的数据:将多个值封装为 Pair(或自定义数据类,更易读)
Pair(newName, newAge)
}
.collect {
(latestName, latestAge) ->
// 任意一个值变化时,都会触发此回调,拿到所有值的最新状态
println("ViewModel 中监听到多值变化:")
println("姓名:$latestName,年龄:$latestAge")
// 执行业务逻辑(如:同时校验姓名和年龄、更新缓存等)
}
}
}

使用LaunchedEffect

LaunchedEffect 是一个副作用函数,它可以在组合发生时启动一个协程。你可以将 MutableState 作为 LaunchedEffect 的键,当 MutableState 的值改变时,LaunchedEffect 会自动取消并重新启动。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import androidx.compose.runtime.*

@Composable
fun MutableStateChangeExample() {
var count by remember { mutableStateOf(0) }

LaunchedEffect(count) {
// 当 count 的值发生变化时,这里的代码会被执行
println("count 的值已更新为: $count")
}

// 模拟改变 count 的值
if (count < 3) {
count++
}
}

代码解释

  • LaunchedEffect(count):把 count 当作键传递给 LaunchedEffect,只要 count 的值改变,LaunchedEffect 内部的代码就会重新执行。
  • println("count 的值已更新为: $count"):当 count 变化时,打印出更新后的值。

对比

比如我们想在subjectId该百年的时候加载数据,使用下面两种方式

vm中使用snapshotFlow把值转为流监听变化

1
2
3
4
5
6
7
8
9
init {
// 监听 subjectId 的变化
viewModelScope.launch {
snapshotFlow { subjectId.value }
.collect { subjectId ->
changeSubjectAction()
}
}
}

Compose中使用LaunchedEffect监听变化

1
2
3
LaunchedEffect(vm.subjectId.value) {
vm.changeSubjectAction()
}

个人推荐snapshotFlow的方式。

原因是

LaunchedEffect 是在值变化的时候会触发,但是同时在组件重组的时候也会触发,这就导致值没变也可能会触发多次事件。

所以更推荐使用snapshotFlow