当前位置:   article > 正文

Compose基础-SideEffect(二)_compose sideeffect

compose sideeffect

1. 前言

Compose基础-Side-effect(一)中,我们学习了几个常用的Side-effectLaunchedEffectrememberCoroutineScope,以及关键字rememberUpdateState的用法。在本篇文章中,我们将介绍剩下几个常用的Side-effect相关关键字的用法。

2. DisposableEffect

对于有些Side-effect,在其key值变化或者composable函数离开Composition时,需要对一些资源等进行清理。这种情况下,可以使用谷歌提供的DisposableEffect。其特点如下:

  • DisposableEffect的key值变化时,当前EffectonDispose会被调用,此时可以在此函数中对资源进行清理;同时DisposableEffect会被重启,此时可以重新申请资源等。

  • DisposableEffect中必须包含onDispose语句,否则IDE会出现编译时错误。

以下是使用DisposableEffect的一个示例,该DisposableEffectLifecycleOwner注册了回调,用于监听一些生命周期事件便于数据统计。代码如下:

@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 */
}
  • 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
  • 30
  • 31
  • 32
  • 33

DisposableEffect常用于抽取一些需要在composable函数离开Composition或者key值变化时清理资源的场景,例如监听的反注册。

3. SideEffect

有时候,我们需要将Compose状态共享给非Compose管理的对象。此时,Google建议我们使用SideEffect函数,因为每次recompose成功时都会调用该函数。SideEffect特点如下:

  • Composition操作失败时,能保证SideEffect中的非Compose管理对象的状态和Composition中的状态一致。

  • SideEffectLayoutNode被创建并插入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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4. produceState

有时候我们会使用协程来获取数据,例如Flow,但是该数据是在Composition中使用。为了使用这些数据,我们需要使用produceState来将non-Compose state来转变为Compose stateproduceState特点如下:

  • 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)
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5. derivedStateOf

当一个状态由另外几个状态计算或者推导得到时,使用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()
}
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34

input1或者input2有变化时,result1会被重新计算。此时ContentWrap button会被打印,但是recompose DevitedStateCompose不会被打印。即展示结果的ContentWraprecompose,但是DevitedStateCompose函数未recompose

6. snapshotFlow

由于Flow强大的数据操作能力,有时候我们想将State转化为Flow,此时我们需要使用snapshotFlow来将State转化为FlowsnapshotFlow的特点如下:

  • snapshotFlow中读取的State发生变化时,snapshotFlow会重新生成Flow

以下是一个示例。

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在上述代码中,如果firstVisibleItemIndex发生了变化,那snapshotFlow会将listState.firstVisibleItemIndex转化为Flow

7. 小结

本文主要介绍了DisposableEffectSideEffectproduceStatederivedStateOfsnapshotFlow五种Side-effect的作用和使用场景。其重点如下:

  • DisposableEffect用于在离开composition时清理一些资源。

  • SideEffect用于将Compose状态共享给非Compose管理的对象。

  • produceState用于将non-Compose state来转变为Compose state

  • derivedStateOf用于记录几个状态计算或者推导得到的状态。

  • snapshotFlow用于将State转化为Flow

8. 参考文档

Side-effects in Compose

Tricky refactoring of Jetpack Compose code — be careful with side effects

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/254147
推荐阅读
相关标签
  

闽ICP备14008679号