赞
踩
在Compose基础-Side-effect(一)中,我们学习了几个常用的Side-effect:LaunchedEffect和rememberCoroutineScope,以及关键字rememberUpdateState的用法。在本篇文章中,我们将介绍剩下几个常用的Side-effect相关关键字的用法。
对于有些Side-effect,在其key值变化或者composable函数离开Composition时,需要对一些资源等进行清理。这种情况下,可以使用谷歌提供的DisposableEffect。其特点如下:
当DisposableEffect的key值变化时,当前Effect的onDispose会被调用,此时可以在此函数中对资源进行清理;同时DisposableEffect会被重启,此时可以重新申请资源等。
DisposableEffect中必须包含onDispose语句,否则IDE会出现编译时错误。
以下是使用DisposableEffect的一个示例,该DisposableEffect为LifecycleOwner注册了回调,用于监听一些生命周期事件便于数据统计。代码如下:
@Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // Safely update the current lambdas when a new one is provided val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) // If `lifecycleOwner` changes, dispose and reset the effect DisposableEffect(lifecycleOwner) { // Create an observer that triggers our remembered callbacks // for sending analytics events val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } // Add the observer to the lifecycle lifecycleOwner.lifecycle.addObserver(observer) // When the effect leaves the Composition, remove the observer onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* Home screen content */ }
DisposableEffect常用于抽取一些需要在composable函数离开Composition或者key值变化时清理资源的场景,例如监听的反注册。
有时候,我们需要将Compose状态共享给非Compose管理的对象。此时,Google建议我们使用SideEffect函数,因为每次recompose成功时都会调用该函数。SideEffect特点如下:
在Composition操作失败时,能保证SideEffect中的非Compose管理对象的状态和Composition中的状态一致。
SideEffect在LayoutNode被创建并插入layout tree后才会被调用。
以下是使用SideEffect的一个示例。分析库允许通过将自定义元数据(在此示例中为“用户属性”)附加到所有后续分析事件,来细分用户群体。如需将当前用户的用户类型传递给分析库,需要使用 SideEffect 更新其值。
@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
val analytics: FirebaseAnalytics = remember {
/* ... */
}
// On every successful composition, update FirebaseAnalytics with
// the userType from the current User, ensuring that future analytics
// events have this metadata attached
SideEffect {
analytics.setUserProperty("userType", user.userType)
}
return analytics
}
有时候我们会使用协程来获取数据,例如Flow,但是该数据是在Composition中使用。为了使用这些数据,我们需要使用produceState来将non-Compose state来转变为Compose state。produceState特点如下:
当produceState进入Composition时,获取数据的任务被启动,当其离开Composition时,该任务被取消。
尽管produceState创建了一个协程,它也可以用于获取non-suspending数据源。
以下示例展示了如何利用produceState从网络加载一张图片。loadNetworkImage这个函数返回了一个State,这个State可以被用于其他composable函数。
@Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository ): State<Result<Image>> { // Creates a State<T> with Result.Loading as initial value // If either `url` or `imageRepository` changes, the running producer // will cancel and will be re-launched with the new inputs. return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { // In a coroutine, can make suspend calls val image = imageRepository.load(url) // Update State with either an Error or Success result. // This will trigger a recomposition where this State is read value = if (image == null) { Result.Error } else { Result.Success(image) } } }
当一个状态由另外几个状态计算或者推导得到时,使用derivedStateOf来记录结果状态,此时作为条件的状态我们称为条件状态。当任意一个条件状态更新时,结果状态都会重新计算。以下是一个示例。
@Composable fun DevitedStateCompose() { val input1: MutableState<String> = remember{mutableStateOf("")} val input2: MutableState<String> = remember{mutableStateOf("")} val result1: State<String>= derivedStateOf{"${input1.value} + ${input2.value}"} Column{ ContentWrap{ Column{ TextField( value = input1.value, onValueChange = {input1.value = it } ) TextField( value = input2.value, onValueChange = {input2.value = it } ) Log.d("DevitedStateTest", "recompose Column") } } ContentWrap{ Text(result1.value) Log.d("DevitedStateTest", "ContentWrap button") } } Log.d("DevitedStateTest", "recompose DevitedStateCompose") } @Composable fun ContentWrap(content: @Composable () -> Unit) { content() }
当input1或者input2有变化时,result1会被重新计算。此时ContentWrap button会被打印,但是recompose DevitedStateCompose不会被打印。即展示结果的ContentWrap被recompose,但是DevitedStateCompose函数未recompose。
由于Flow强大的数据操作能力,有时候我们想将State转化为Flow,此时我们需要使用snapshotFlow来将State转化为Flow。snapshotFlow的特点如下:
以下是一个示例。
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
在上述代码中,如果firstVisibleItemIndex发生了变化,那snapshotFlow会将listState.firstVisibleItemIndex转化为Flow。
本文主要介绍了DisposableEffect,SideEffect,produceState,derivedStateOf,snapshotFlow五种Side-effect的作用和使用场景。其重点如下:
DisposableEffect用于在离开composition时清理一些资源。
SideEffect用于将Compose状态共享给非Compose管理的对象。
produceState用于将non-Compose state来转变为Compose state。
derivedStateOf用于记录几个状态计算或者推导得到的状态。
snapshotFlow用于将State转化为Flow。
Tricky refactoring of Jetpack Compose code — be careful with side effects
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。