状态管理与重组
更新: 1/31/2026 字数: 0 字 时长: 0 分钟
状态管理
1.创建 MutableState
可以使用 mutableStateOf(0)创建 counter:
val counter: MutableState<Int> = mutableStateOf(0)还可以使用解构,返回 T 类型的 value 以及 (T) -> Unit类型的 set 方法:
val (counter, setCounter) = mutableStateOf(0)
setCounter(1) // 使用setCounter更新值,而不用counter.value获取也可以使用属性代理创建,Kotlin 的 by 关键字直接获取 Int 类型的 counter。
var counter by mutableStateOf(0)通过 by 关键字,可以重写 getValue 和 setValue 最终代理为对 value 的操作。
使用 remember 缓存状态
我们在更新值时,通常用了 remember 进行包裹:
var progress by remember { mutableFloatStateOf(0.1f) }这里 remember 就是获取更新值的关键,因为如果移除的话就会每次重新创建一个初始值为 0 的 MutableState 对象,无法继承前次组合的状态。remember 则可以解决这个问题,在 Composable 首次创建时,remember 中计算得到的数据会自动缓存,再次重组时就会返回之前已缓存的数据。
状态上提
常用于某 Composable 组件需要多处复用,但同时我们希望更改一些逻辑使得具有更高的复用性,状态管理逻辑也有调用方实现,我们可以将 Statefule 改造为 Stateless,Stateful 是指内部持有或者访问了一些状态,而 Stateless 的重组只能来自上层 Composable 的调用。
Stateless 由于不耦合任何业务逻辑,所以功能更加纯粹,相对于 Stateful 的可复用性更好,对测试也更加友好。
比如下面用一个计数器组件实现状态上提:
@Composable
fun CounterComponent(
counter: Int,
onIncrement: () -> Unit,
onDecrement: () -> Unit
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
"Click the buttons",
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Text(
"$counter",
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = Typography.bodyMedium
)
Row {
Button(
onClick = { onDecrement() },
Modifier.weight(1f)
) {
Text("-")
}
Spacer(Modifier.width(16.dp))
Button(
onClick = { onIncrement() },
Modifier.weight(1f)
) {
Text("+")
}
}
}
}
@Composable
fun CounterScreen() {
var counter by remember { mutableStateOf(0) }
CounterComponent(counter = counter, { counter++ }) {
if (counter > 0) {
counter--
}
}
}CounterScreen 在调用 CounterComponent 时为其注入了 counter,onIncrement(),onDecrement 几个参数。
状态上提还有助于实现单一数据源,状态总是自上而下流动,事件总是自下向上传递。
但是更推荐管理状态时使用 ViewModel 因为参数往往都很复杂。
添加依赖:
lifecycle = "2.8.7"
compose-lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle"}创建 ViewModel:
class CounterViewModel : ViewModel() {
private val _counter = mutableStateOf(0)
val counter: State<Int> = _counter
fun increment() {
_counter.value++
}
fun decrement() {
if(_counter.value > 0) {
_counter.value--
}
}
}使用:
@Composable
fun CounterScreen() {
val viewmodel: CounterViewModel = viewModel()
CounterComponent(counter = viewmodel.counter.value, viewmodel::increment, viewmodel::decrement)
}状态分层管理
使用 Stateful 管理状态
简单的 UI 状态以及逻辑可以直接在 Composable 中管理(使用 rememberState):
fun MyApp() {
ComposeStudyTheme {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { innerPadding ->
coroutineScope.launch {
snackbarHostState.showSnackbar("Hello")
}
}
}
}使用 StateHolder 管理状态
可以创建一个专门的 StateHolder 用来剥离 UI 逻辑:
class MyAppState(
val snackbarHostState: SnackbarHostState,
val navController: NavHostController,
private val resources: Resources,
...
) {
val bottomBarTabs = ...
val shouldShowBottomBar: Boolean
get() = ...
fun navigateToBottomBarRoute(route: String) { }
fun showSnackbar(message: String) { }
}
// 创建remember方法便于在Composable中使用
fun rememberMyAppState(...
) = remember ( ... ) {
MyAppState(...)
}使用 ViewModel 管理状态
可以使用 ViewModel 管理与 UI 状态无关的数据与逻辑,也能实现全局共享。
// 使用private set避免来自ViewModel外对于UIState的更新
var uiState by mutableStateOf(0)
private setLiveData,RxJava,Flow 转 State:
LiveData.observeAsState: androidx.compose.runtime: runtime-livedata: $compose version
Flow.collectAsState(): 自带
Observable.subscribeAsState(): androidx.compose.runtime:runtime-rxjava3: $compose version
Composable 生命周期
- onActive(添加到视图树):首次被执行时
- onUpdate(重组)
- onDispose(从视图树移除)
Composable 副作用
Composable 在执行过程中,凡是会影响外界的操作都属于副作用(Side-Effects),比如弹出 Toast,保存本地文件,访问远程或本地数据等。重组会导致 Composable 频繁反复执行,而副作用显然是不应该跟着重组反复执行。所以 Compose 提供了一系列 API,让副作用 API 只发生在 Composable 生命周期的特定阶段。
副作用 API
DisposableEffect
@Composable
fun backPressHandler(enabled: Boolean = true, onBackPressed: () -> Unit) {
val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
"No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
}.onBackPressedDispatcher
val backCallback = remember {
object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
}
DisposableEffect(backDispatcher) {
backDispatcher.addCallback(backCallback)
onDispose {
//进入onDispose时移除
backCallback.remove()
}
}
}先创建了 OnBackPressedCallback 回调处理返回事件,使用 remember 包裹防止重复创建。
然后在 DisposableEffect 后的 block 内向 OnBackPressedDispatcher 注册返回事件回调。也能接受参数 key,但不能为空:
- 如果 key 为 Unit/true 常量,则只在 OnActive 时执行一次
- 如果为其他变量,则还会再 OnActive 内执行,比如如果 dispatcher 变化,则会注册新的回调。
DisposableEffect 最后必须跟 onDispose 进行首位处理,如注销回调。
使用时就很简单了:
@Composable
fun HomeScreen() {
backPressHandler {
//返回键处理
}
}SideEffect
SideEffect 会在每次成功重组时执行,所以不能用来处理耗时或异步的副作用逻辑。
与直接写在 Composable 里面相比,SideEffect 只会在重组成功时执行,因为重组会触发 Composable 执行,但不一定会成功结束,可能会中途失败。可用来将当前正确的 State 暴露给外部。
异步处理的副作用 API
LaunchedEffect
当副作用中有处理异步任务的需求时,可以使用 LaunchedEffect。 在 Composable 进入 OnActive 时,LaunchedEffect 会启动协程执行内容,也可以启动子协程或调用挂起函数,当进入 OnDispose 时协程会自动取消,故不需要实现 OnDispose。
同时 LaunchedEffect 支持参数 key 的设置,当 key 发生变化时,协程自动结束,同时开启新的协程。
rememberCoroutineScope
LaunchedEffect 只能在 Composable 中调用,如果想在非 Composable 环境中使用协程,比如 Button 的 OnClick 中使用协程显示 snackBar,可以使用这个:
@Composable
fun SnackbarExample() {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) { innerPadding ->
Button(
onClick = {
scope.launch {
snackbarHostState.showSnackbar(
message = "数据已保存",
actionLabel = "撤销"
)
}
},
modifier = Modifier.padding(innerPadding)
) {
Text("显示 Snackbar")
}
}
}这里 scope.launch 会返回一个 CoroutineScope,可以在进入 onDispose 时自动取消。
rememberUpdateState
LaunchedEffect 会在参数 key 变化时启动一个协程,但有时我们不希望协程中断,只要能实时获取最新状态,此时可以借助 rememberUpdateState 实现:
@Composable
fun MyScreen(onTimeout: () -> Unit) {
val currentOnTimeOut by rememberUpdatedState(onTimeout)
LaunchedEffect(Unit) {
delay(1000)
currentOnTimeOut() // 总是能收到最新的onTimeout
}
}这里 block 开始执行后,不会因 MyScreen 重组而中断。但是当执行到 currentOnTimeout()时,仍能获取最新的 OnTimeOut 实例。
snapshotFlow
用来转成 Coroutine Flow,观察参数 key 来通知状态变化,每当 State 发生变化时,flow 就会发送新数据,如果无变化则不发射。
val pagerState = rememberPagerState()
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }
.collect { page ->
// 发生变化
}
}状态创建的副作用 API
produceState
这个 API 用来将一个外部的数据源转成 State。
@Composable
fun loadNetWworkImage(
url: String,
imageRepository: ImageRepository
) : State<Result<Image>> {
return produceState<Result<Image>>(initialValue = Result.Loading) {
val image = imageRepository.load(url)
value = if (image == null) {
Result.Error
} else {
Result.success(image)
}
}
}上面代码中,通过网路请求获取图片并用 produceState 转换为 State<Result<Image>>,当 Image 获取失败时,会返回 Result.Error。produceState 观察 url 和 imageRepository 两个参数,当发生变化时,producer 会重新执行。
derivedStateOf
用来将一个/多个 State 转成另一个 State。derivedStateOf{...}的 block 中可以依赖其他 State 创建并返回一个 DerivedState,当 block 中依赖的 State 发生变化时,会更新此 DerivedState,依赖此 State 的所有 Composable 会因其变化而重组。
@Composable
fun SearchScreen() {
val postList = remember { mutableStateListOf<String>() }
var keyword by remember { mutableStateOf("") }
val result by remember {
derivedStateOf { postList.filter { it.contains(keyword, false) } }
}
Box(Modifier.fillMaxSize()) {
LazyColumn {
item(result) { /* ... */ }
}
}
}上面代码在 derivedStateOf{...}的 block 内部实现了检索逻辑,当 postList 或者 keyword 任意变化时,result 会更新。
当一个状态的变化需要造成副作用终止时,才将其添加为观察参数 key,否则应该将其使用rememberUpdateState包装后,在副作用中使用,以避免打断执行中的副作用。
