Jetpack Compose - MutableState/by/remember/rememberSaveable及状态变化监听

前言

在Jetpack Compose我们会看到这两种定义变量的方式,很相似,但是使用起来却不一样。

1
2
val name1 = remember { mutableStateOf("") }
var name2 by remember { mutableStateOf("") }

我们可以拆分成三个部分

  • by/=
  • remember/mutableStateOf
  • mutableStateOf/mutableStateListOf

下面分别说一下这三者的作用。

区别

类型

  • name1 是 MutableState<String> 他是可以传入子组件中,子组件中修改后,父组件中也能获取到更新后的值。
  • name2 是 String 类型,传入子组件的是而不是 MutableState 对象,子组件不能修改它的值,因为传入的值的类型是val

by

属性委托是 Kotlin 提供的一种特性,它允许把属性的 get()set() 方法的实现委托给另一个对象。借助这种方式,能够避免编写大量样板代码,增强代码的复用性与可维护性。

基本语法格式为:

1
var <属性名>: <类型> by <委托对象>

by 关键字把 name2 属性的 get()set() 方法的实现委托给了 remember { mutableStateOf("") } 返回的 MutableState 对象。

具体来说:

  • 读取属性值:当读取 name2 的值时,实际上调用的是 MutableState 对象的 getValue 方法,该方法会返回 MutableState 对象的 value 属性。
  • 设置属性值:当给 name2 赋值时,实际上调用的是 MutableState 对象的 setValue 方法,该方法会更新 MutableState 对象的 value 属性。

remember/rememberSaveable

在Compose中,rememberrememberSaveable都是用于保存可组合函数的状态的方法,但它们在如何保存状态以及在什么情况下会重新计算状态上有所不同。

简言之

  • remember在重绘的时候保留状态,建议组件内的属性都添加remember
  • rememberSaveable不仅重绘能保留状态,销毁重建也能保留状态(例如旋转屏幕)。

remember: 这个函数在组合函数的生命周期内始终保持相同的状态。

这意味着,每次组合函数重新调用时,它都会使用先前保存的状态值,而不会重新计算它。这样能避免不必要的计算开销。

这对于静态数据或者不会因用户交互而改变的数据很有用。如果状态的改变不需要在组件生命周期之外持久化,remember是一个更轻量级的选择。

1
var password by remember { mutableStateOf("") }

rememberSaveable: 这个函数也会保存状态,但它还会将状态持久化,以便在应用程序进入后台或被销毁后,能够恢复该状态。

这对于需要跨配置更改(例如旋转屏幕)或者应用程序生命周期的状态非常有用。

它会将状态保存在Bundle中,以确保状态的持久化。

1
var password by rememberSaveable { mutableStateOf("") }

因此,rememberSaveable提供了对状态的持久化支持,而remember则仅在组件生命周期内保存状态。

选择使用哪种取决于您需要的状态是否需要在应用程序重新启动后保持不变。

mutableStateOf/mutableStateListOf

mutableStateOf

mutableStateOf 是 Jetpack Compose 中的一个函数,用于创建可变的状态。

它的作用是创建一个可以被修改的状态,并且当状态发生改变时,Compose 会重新计算并更新相关的 UI。

具体来说,mutableStateOf 函数接受一个初始值作为参数,并返回一个包含该初始值的 MutableState 对象。

MutableState 对象具有 value 属性,可以读取和修改该状态的值。

MutableState 对象的值发生改变时,Compose 会根据新的状态重新计算 UI,以确保 UI 反映最新的状态。

注意

只有组件内使用了countState.value才可以,如果只是使用了countState也不行。

例如,假设我们有一个 mutableStateOf 对象来表示一个计数器的值:

1
val countState = remember { mutableStateOf(0) }

然后我们可以通过修改 countState.value 的值来更新计数器的状态:

1
countState.value += 1

每当 countState.value 的值发生改变时,与该状态相关联的 UI 将会重新计算并更新,从而反映最新的计数器值。

总的来说:

mutableStateOf 的作用是在 Jetpack Compose 中创建可变的状态,以便动态更新 UI,并确保 UI 反映最新的状态值。

注意

如果是对象,对象的值变并不会触发渲染,必须改变引用才可以。

比如

1
2
3
var currentPath by remember { mutableStateOf(Path()) }

currentPath = currentPath.copy()

mutableStateListOf

1
val drawPaths = remember { mutableStateListOf<DrawPath>() }

返回的类型是SnapshotStateList<T>

mutableStateListOf 在以下情况下会触发重新渲染:

  • 列表元素的引用发生变化。
  • 列表的结构发生变化,如添加、删除或移动元素。

监听变化

在 Jetpack Compose 里,有多种方式可以监听 MutableState 的变化,下面为你详细介绍不同的实现方法:

使用 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 变化时,打印出更新后的值。

使用 derivedStateOf

derivedStateOf 可用于创建一个派生状态,当依赖的 MutableState 发生变化时,派生状态也会更新。你可以监听派生状态的变化。

示例代码

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

@Composable
fun DerivedStateChangeExample() {
var inputText by remember { mutableStateOf("") }
val upperCaseText = remember { derivedStateOf { inputText.uppercase() } }

LaunchedEffect(upperCaseText.value) {
// 当 upperCaseText 的值发生变化时,这里的代码会被执行
println("大写文本已更新为: ${upperCaseText.value}")
}

// 模拟改变 inputText 的值
if (inputText.length < 3) {
inputText += "a"
}
}

代码解释

  • derivedStateOf { inputText.uppercase() }:创建一个派生状态 upperCaseText,它依赖于 inputText,当 inputText 改变时,upperCaseText 会自动更新为 inputText 的大写形式。
  • LaunchedEffect(upperCaseText.value):监听 upperCaseText 的变化,当它的值改变时,执行内部代码。

使用 produceState

produceState 可以用来管理异步状态,当依赖的 MutableState 发生变化时,它会重新计算状态。

示例代码

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

@Composable
fun ProduceStateChangeExample() {
var number by remember { mutableStateOf(0) }
val squaredNumber = produceState(initialValue = 0) {
// 当 number 的值发生变化时,这里的代码会被执行
value = number * number
}

LaunchedEffect(squaredNumber.value) {
println("number 的平方已更新为: ${squaredNumber.value}")
}

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

代码解释

  • produceState(initialValue = 0) { value = number * number }:创建一个状态 squaredNumber,当 number 改变时,重新计算 number 的平方并更新 squaredNumber 的值。
  • LaunchedEffect(squaredNumber.value):监听 squaredNumber 的变化,当它的值改变时,执行内部代码。

转换为 Flow 监听(灵活适配生命周期)

如果需要更灵活的监听(如配合生命周期、在 ViewModel 中处理等),可以使用 snapshotFlowMutableState 转换为 Flow,再通过 collect 监听变化。

示例:转换为 Flow 并结合生命周期监听

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
26
27
28
29
import androidx.compose.runtime.*
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.compose.runtime.snapshotFlow

@Composable
fun MutableStateToFlowExample() {
var count by remember { mutableStateOf(0) }
val lifecycleOwner = LocalLifecycleOwner.current

// 将 MutableState 转换为 Flow
val countFlow = snapshotFlow { count }

// 结合生命周期收集 Flow(避免内存泄漏)
LaunchedEffect(lifecycleOwner, countFlow) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
countFlow.collect { newCount ->
println("count 变化为:$newCount")
// 可执行其他操作(如更新数据库、发送事件等)
}
}
}

// 一个按钮,用于修改 count
Button(onClick = { count++ }) {
Text("count: $count")
}
}

优势

  • snapshotFlow 会在 MutableState 变化时发射新值,适配 Compose 的状态快照系统。
  • 结合 repeatOnLifecycle 可以跟随生命周期自动暂停 / 恢复收集,避免内存泄漏。

非 Composable 环境中监听(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 层的状态变化时(如输入校验、实时搜索等),这种方式非常合适。