如何通过Side Effects来使得你使用Compose变的得心应手?

news2024/9/23 7:28:47

作者:clwater

虽然我使用Compose已经有了一段时间的, 但我还是觉得使用起来束手束脚的. 究其原因, 大概是coding时的思路还没有完全转换过来, 还没有沉浸在"Compose is Function"之中. 和我们熟悉的View不同, 当我们调用Compose之后, 我们就失去了它的修改器, 而Compose也只能按照我们之前设计好的功能去响应我们的操作.

除此之外阶段(Phases), 也是一个可以使得你的Compose变的得心应手的入口, 虽然这篇文章不会进行相关介绍, 但我也会在后续的文章中进行介绍.

在了解Side Effects之前, 我们需要先简单了解一下Lifecycle of composables

Lifecycle of composables

相信大家对Lifecycle都十分的熟悉, 在我们的Android项目开发时, Activity及Fragment的Lifecycle对我们的功能实现提供了极大的帮助. 通过Lifecyle我们可以很便捷的处理页面不同时期的状态. 想象一下, 如果将Compose变为一个Activity, 那么许多的功能我们都可以通过Lifecycle来完成. 当然, Compose的Lifecyle并不如Activity丰富, 而且设计的思路也不尽相同, 但这都不能阻止Compose的Lifecycle来帮助我们控制Compose.

先看一下官方提供的Lifecycle of composables说明, 简单来说, Lifecycle of composables包含以下三个部分:

  • 进入组合(创建)
  • 执行 0 次或多次重组(重组)
  • 退出组合(销毁)

如果想要了解Side Effects的话, 对于Lifecycle of composables了解到这些就足够了.(写完文章再看这里, 不, 完全不够.)

和Activity及Fragment的Lifecycle不同的是, Side Effects并没有Lifecycle这么的泾渭分明, 很多的功能可以用不同的Side Effects来实现, 也使得Side Effects用起来既顺手又疑惑.(当然, 这都是现阶段大家都还在探索中的情况)

下面就让我们来看一看Side Effects到底是什么, 又有着什么样的功能.

Side Effects

Side Effects有时被译为"副作用"(如果你打开官方文档, 你会发现左侧的列表中还是副作用), 当然, 大部分情况下都被翻译为"附带效应."(个人认为附带效应是更加准确的, 毕竟如果是副作用的话, 那说明其作用都是不应该出现的, 但是作为附带效应的话, 其作用是否应该出现, 取决于我们如何去操作).

借由官方文档对其的定义和说明.可以看到以下两个需要关注的地方.

一是附带效应"应从可组合项生命周期的受控环境中调用", 也就是Lifecycle of composables中调用. 这也是需要先了解Lifecycle of composables的原因.

二是应该"以可预测的方式执行这些附带效应", 这是不应该翻译为副作用的原因, Side Effects的操作应该是可控的, 有效的.

可能在你的了解或实践的过程中, 你会不止一次的疑惑, “这个功能我为什么不能直接在OnClick中完成? 他们之中有什么区别?” 这也是我在整个过程中经常疑惑的地方, 答案也是很简单, 所有的Side Effects都是处理果的动作, 它们都是由其它因引起的,

说了这么多的定义, 不如来看看这些Side Effects都是如何通过可组合项生命周期的受控环境中调用来达到以可预测的方式执行这些附带效应并帮助我们的Compose变得得心应手.

LaunchedEffect 在某个可组合项的作用域内运行挂起函数

“如需从可组合项内安全调用挂起函数,请使用 LaunchedEffect 可组合项。当 LaunchedEffect 进入组合时,它会启动一个协程,并将代码块作为参数传递。如果 LaunchedEffect 退出组合,协程将取消。如果使用不同的键重组 LaunchedEffect(请参阅下方的重启效应部分),系统将取消现有协程,并在新的协程中启动新的挂起函数。”

可以看到LaunchedEffect具有以下特点:

  • 运行suspend functions
  • 进入组合时候执行
  • 退出组合时候取消
  • 具有重启效应

运行suspend functions

关于suspend functions, 相信大家都还是十分熟悉的, 其中一个十分重要的特点就是, 只能通过suspend functions来调用suspend functions. 下面我们尝试下在不同的位置调用suspend functions的效果.

    //https://gist.github.com/clwater/5454deed3ae258ba3980d260a0ff3299
    suspend fun suspendFunTest() {
        Log.d("clwater", "suspendFunTest Start")
        delay(3000)
        Log.d("clwater", "suspendFunTest Finish")
    }

    @Composable
    fun TestLifecycleCompose() {
        LaunchedEffect(Unit) {
            // 1️⃣ Success
            suspendFunTest()
        }
        // 2️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function    
        suspendFunTest()
        Button(
            onClick = {
            // 3️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function    
            suspendFunTest()
        }) {
            Text(text = "suspendFunTest")
        }
    }

我们可以看到以上三个位置仅有1️⃣的位置是有效的, 放到2️⃣和3️⃣都会报错. 很明显2️⃣和3️⃣的位置都不能调用suspend functions.

进入组合时候执行

想到我们前面提到的Lifecycle of composables, 我们就可以感知到我们的Composeables进入组合的事件了!

    //https://gist.github.com/clwater/4b9b1bedca4365732de7d8e2c8519190
    @Composable
    fun TestLifecycleCompose() {
        LaunchedEffect(Unit) {
            Log.d("clwater", "TestLifecycleCompose Enter")
        }
        Text(text = "TestLifecycleCompose")
    }

当我们调用这个Composeables时, 我们就可以看到以下的log信息了

2023-05-18 16:10:27.432 30584-30584 clwater                 com.clwater.compose_learn_1       D  TestLifecycleCompose Enter

退出组合时候取消

我们知道suspend functions都是可以取消的, 同样LaunchedEffect既可以感知Composeables进入的事件, 也会在Composeables离开的时候取消.

    // https://gist.github.com/clwater/440af58717e699d4963f915f520d494a
    @Composable
    fun TestLifecycleCompose() {
        var isShow by remember {
            mutableStateOf(true)
        }

        Column {
            if (isShow) {
                TestLifecycleComposeText()
            }

            Button(
                onClick = { isShow = !isShow }
            ) {
                Text(text = "TestLifecycleCompose show: $isShow")
            }
        }
    }

    @Composable
    fun TestLifecycleComposeText() {
        LaunchedEffect(Unit) {
            Log.d("clwater", "TestLifecycleCompose Enter")
            try {
                delay(10 * 1000)
                Log.d("clwater", "TestLifecycleCompose Finish")
            } catch (e: Exception) {
                Log.d("clwater", "TestLifecycleCompose Error: $e")
            }
        }
        Text(text = "TestLifecycleCompose")
    }

如果我们启动后不做任何操作, 或者超过10s后再次点击, 我们会看到如下的log

2023-05-18 16:15:27.449 30584-30584 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Enter
2023-05-18 16:15:37.453 30584-30584 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Finish

但是当我们在10s再次点击按钮使得上面的Composeables不在显示的时候, 我们就可以看到出现了以下的log

2023-05-18 16:22:03.698 31930-31930 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Enter
2023-05-18 16:22:04.895 31930-31930 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@67197cb

我们可以很清除的看到我们在LaunchedEffect运行的内容, 因为对应Composeables离开而被取消.

重启效应

“Compose 中有一些效应(如 LaunchedEffect、produceState 或 DisposableEffect)会采用可变数量的参数和键来取消运行效应,并使用新的键启动一个新的效应。”

重启效应不单单只有LaunchedEffect具有, 但是相关的效果都表现一致, 这里我们针对LaunchedEffect的重启效应进行详细分析.

值得注意的是, 当我们通过重启效应来启动新的效应的时候, 我们旧的效应(同一个键)会被取消. 类似前面退出组合时候取消的效果.

    @Composable
    fun TestLifecycleCompose() {
        var clickCount by remember {
            mutableStateOf(0)
        }

        LaunchedEffect(clickCount) {
            Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount")
        }

        Column {
            Button(onClick = { clickCount++ }) {
                Text("clickCount $clickCount")
            }
        }
    }

当我们点击按钮的时候, 我们可以在日志中看到如下的输出.当然, 你可以通过在onClick中打印这些内容来实现同样的功能. 不过这两种实现的方式侧重点是不同的. 在onClick中, 你侧重的内容是点击按钮, 而在LaunchedEffect中, 你侧重的是clickCount值的变化.

2023-05-19 10:21:18.864 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 0
2023-05-19 10:21:26.114 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 1
2023-05-19 10:21:26.387 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 2
2023-05-19 10:21:26.594 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 3
2023-05-19 10:21:26.806 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 4
2023-05-19 10:21:27.005 30841-30841 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 5

简单的, 如果我们增加对取消时的异常捕获, 我们就能看到下面类似的log

    @Composable
    fun TestLifecycleCompose() {
        var clickCount by remember {
            mutableStateOf(0)
        }

        LaunchedEffect(clickCount) {
            try {
                Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount")
                delay(1 * 1000)
                Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount finish")
            } catch (e: Exception) {
                Log.d("clwater", "TestLifecycleCompose Error: $e")
            }
        }

        Column {
            Button(onClick = { clickCount++ }) {
                Text("clickCount $clickCount")
            }
        }
    }

2023-06-05 14:30:38.948 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 0
2023-06-05 14:30:39.951 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 0 finish
2023-06-05 14:30:46.362 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 1
2023-06-05 14:30:47.363 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 1 finish
2023-06-05 14:30:47.432 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 2
2023-06-05 14:30:47.597 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@ac45c38
2023-06-05 14:30:47.597 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 3
2023-06-05 14:30:47.776 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@ed9b9fe
2023-06-05 14:30:47.776 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 4
2023-06-05 14:30:47.965 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@d0a879d
2023-06-05 14:30:47.965 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 5
2023-06-05 14:30:48.968 17377-17377 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose clickCount: 5 finish

rememberCoroutineScope:获取组合感知作用域,以便在可组合项外启动协程

"由于 LaunchedEffect 是可组合函数,因此只能在其他可组合函数中使用。为了在可组合项外启动协程,但存在作用域限制,以便协程在退出组合后自动取消,请使用 rememberCoroutineScope。 此外,如果您需要手动控制一个或多个协程的生命周期,请使用 rememberCoroutineScope,例如在用户事件发生时取消动画。

rememberCoroutineScope 是一个可组合函数,会返回一个 CoroutineScope,该 CoroutineScope 绑定到调用它的组合点。调用退出组合后,作用域将取消。"

可以看到rememberCoroutineScope具有以下特点:

  • 在composable之外启动协程
  • 可手动控制一个或多个协程的生命周期
  • 是一个composable function

在composable之外启动协程

最核心的作用就是这个, 在composable之外启动协程, 我们还是尝试在不同的部分来启动一个suspend functions.

    @Composable
    fun TestLifecycleCompose() {
        // 1️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function
        suspendFunTest()

        val scope = rememberCoroutineScope()

        Button(onClick = {
            // 2️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function
            suspendFunTest()

            scope.launch {
                // 3️⃣ Success
                suspendFunTest()
            }
        }) {
            Text(text = "suspendFunTest")
        }
    }

和前面的例子类似的, 在位置1️⃣和2️⃣都会报错, 仅在3️⃣的位置才能正常使用. 当我通过手动触发某些协程的时候, 这个方法就变得十分的好用. 比如在官方说明中点击某个按钮后再Scaffold中调用showSnackbar

@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(scaffoldState = scaffoldState) {
        Column {
            /* ... */
            Button(
                onClick = {
                    // Create a new coroutine in the event handler to show a snackbar
                    scope.launch {
                        scaffoldState.snackbarHostState.showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}

可手动控制一个或多个协程的生命周期/是一个composable function

前言中我们有提及, 不论是哪种Side Effects, 都是某种来处理果的过程, 但是实际的开发过程中, 我们不可避免的需要更加精细的对协程进行控制, 虽然前面提及到的点有两个, 但是我认为这两个放在一个例子中可以更好的帮助大家进行理解.

    @Composable
    fun TestLifecycleCompose() {
        var showChild by remember {
            mutableStateOf(true)
        }

        var showParent by remember {
            mutableStateOf(true)
        }

        val scope = rememberCoroutineScope()

        Column {
            Row {
                Button(onClick = {
                    showParent = false
                }) {
                    Text(text = "Hide Parent")
                }

                Button(onClick = {
                    showChild = false
                }) {
                    Text(text = "Hide Child")
                }
            }

            if (showParent) {
                Button(onClick = {
                    scope.launch {
                        try {
                            Log.d("clwater", "TestLifecycleCompose")
                            delay(1000 * 1000)
                        } catch (e: Exception) {
                            Log.d("clwater", "TestLifecycleCompose Error: $e")
                        }
                    }
                }) {
                    Text(text = "Parent")
                }
            }

            if (showChild) {
                TestLifecycleComposeChild()
            }
        }
    }

    @Composable
    fun TestLifecycleComposeChild() {
        val scope = rememberCoroutineScope()

        Button(onClick = {
            scope.launch {
                try {
                    Log.d("clwater", "TestLifecycleComposeChild")
                    delay(1000 * 1000)
                } catch (e: Exception) {
                    Log.d("clwater", "TestLifecycleComposeChild Error: $e")
                }
            }
        }) {
            Text(text = "Child")
        }
    }

通过log我们可以看到, 不论先关闭哪一个按钮, log的结果都是一样的(子composables被取消), 因为rememberCoroutineScope返回的CoroutineScope被绑定到调用它的组合点, 所以虽然看起来TestLifecycleCompose没有内容被显示, 但是这个function还没有退出, 所以调用的协程还一直在执行, 而TestLifecycleComposeChild却完完全全的被执行退出, 所以绑定在TestLifecycleComposeChild的scope就会被取消.

2023-06-05 16:31:28.529 22058-22058 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose
2023-06-05 16:31:29.341 22058-22058 clwater                 com.clwater.compose_learn_1          D  TestLifecycleComposeChild
2023-06-05 16:31:34.309 22058-22058 clwater                 com.clwater.compose_learn_1          D  TestLifecycleComposeChild Error: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@2fc6b1d


2023-06-05 16:31:53.457 22151-22151 clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose
2023-06-05 16:31:53.936 22151-22151 clwater                 com.clwater.compose_learn_1          D  TestLifecycleComposeChild
2023-06-05 16:31:58.985 22151-22151 clwater                 com.clwater.compose_learn_1          D  TestLifecycleComposeChild Error: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@8d47f01

rememberUpdatedState:在效应中引用某个值,该效应在值改变时不应重启

“当其中一个键参数发生变化时,LaunchedEffect 会重启。不过,在某些情况下,您可能希望在效应中捕获某个值,但如果该值发生变化,您不希望效应重启。为此,需要使用 rememberUpdatedState 来创建对可捕获和更新的该值的引用。这种方法对于包含长期操作的效应十分有用,因为重新创建和重启这些操作可能代价高昂或令人望而却步。”

说实话, 第一次看到这个解释的时候我感觉我更加的不理解了, 不过从官方的介绍中, 我们可以看到rememberUpdatedState解决的主要问题是在效应中捕获某个值,不希望效应重启.

这里的我们之间看一下示例代码,

    https://gist.github.com/clwater/214e28128f93d8e491afd189618fea36
    @Composable
    fun DelayCompose(click: Int) {
        val rememberClick = rememberUpdatedState(newValue = click)
        LaunchedEffect(Unit) {
            delay(5000)
            Log.d("clwater", "TestLifecycleCompose click: $click")
            Log.d("clwater", "TestLifecycleCompose rememberClick: ${rememberClick.value}")
        }
    }

    @Composable
    fun TestLifecycleCompose() {
        var lastClick by remember {
            mutableStateOf(-1)
        }

        Column {
            Button(onClick = {
                Log.d("clwater", "onClick 0")
                lastClick = 0
            }) {
                Text(text = "0")
            }

            Button(onClick = {
                Log.d("clwater", "onClick 1")
                lastClick = 1
            }) {
                Text(text = "1")
            }
        }

        DelayCompose(click = lastClick)
    }

代码还是比较简单的, 我们分别尝试先点击两次"按钮0", 后点击两次"按钮1", 已经先点击两次"按钮1", 后点击两次"按钮0". 我们先预测下最后的log中会是什么样子的, 再来看看实际上log的情况.

2023-06-07 13:23:21.551  6400-6400  clwater                 com.clwater.compose_learn_1          D  onClick 0
2023-06-07 13:23:21.826  6400-6400  clwater                 com.clwater.compose_learn_1          D  onClick 0
2023-06-07 13:23:22.314  6400-6400  clwater                 com.clwater.compose_learn_1          D  onClick 1
2023-06-07 13:23:22.649  6400-6400  clwater                 com.clwater.compose_learn_1          D  onClick 1
2023-06-07 13:23:24.415  6400-6400  clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose click: -1
2023-06-07 13:23:24.415  6400-6400  clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose rememberClick: 1

2023-06-07 13:23:42.094  6711-6711  clwater                 com.clwater.compose_learn_1          D  onClick 1
2023-06-07 13:23:42.462  6711-6711  clwater                 com.clwater.compose_learn_1          D  onClick 1
2023-06-07 13:23:42.960  6711-6711  clwater                 com.clwater.compose_learn_1          D  onClick 0
2023-06-07 13:23:43.198  6711-6711  clwater                 com.clwater.compose_learn_1          D  onClick 0
2023-06-07 13:23:46.236  6711-6711  clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose click: -1
2023-06-07 13:23:46.236  6711-6711  clwater                 com.clwater.compose_learn_1          D  TestLifecycleCompose rememberClick: 0

我们可以看到, 没有通过rememberUpdatedState获得值是默认值, 也可以理解为我们首次调用时传入的值, 即使DelayCompose方法没有做任何修饰, 并且入参的内容都不一样. (个人理解是由于Compose对重启的优化, 避免页面被重新绘制多次没有变化的元素), 实际上我们的DelayCompose实际上只执行了一次(你可以在DelayCompose的LaunchedEffect中添加Log来认证一下).

再回来看我们的代码, DelayCompose这个方法实际上在-1入参后就进入了延时, 并不对新的入参做反应了, 这也就导致了我们Log中输出入参信息只有-1, 但通过rememberUpdatedState获取的值, 他会在使用的时候更新当前新的值, 也是的我们在Log中可以看到我们实际点击的情况.

DisposableEffect:需要清理的效应

“对于需要在键发生变化或可组合项退出组合后进行清理的附带效应,请使用 DisposableEffect。如果 DisposableEffect 键发生变化,可组合项需要处理(执行清理操作)其当前效应,并通过再次调用效应进行重置。”

简单来说, 当我们退出组合时会触发DisposableEffect, 我们可以在这里解绑或注销一些资源. 需要注意的是在DisposeableEffect中是无法直接调用suspend functions的, 也就是说DisoposeableEffect并不属于Compose state. 使用起来的话代码也比较简单.

    fun TestLifecycleCompose(obsever: TestObserver) {
        LaunchedEffect(Unit){
            obsever.start()
        }
        DisposableEffect(Unit) {
            onDispose {
                obsever.stop()
            }
        }
    }

上述只是一个最简单的使用, 同时我们主要到, DisposeableEffect也有一个key1, 那么它和LaunchedEffect直接的区别是什么? 还是说DisposeableEffect只是LaunchedEffect加一个onDispose并且不能调用suspend functions的修改版本?

我们试一下以下代码, 并在屏幕中点击按钮, 我们不妨先想象一下最终的Log是什么样子的.

    @Composable
    fun ControlCompose() {
        LaunchedEffect(Unit) {
            Log.d("clwater", "LaunchedEffect(Unit)")
        }
        DisposableEffect(Unit) {
            Log.d("clwater", "DisposableEffect(Unit) out onDispose")
            onDispose {
                Log.d("clwater", "DisposableEffect(Unit) in  onDispose")
            }
        }

        var count by remember {
            mutableStateOf(0)
        }

        Button(onClick = { count++ }) {
            Text(text = "count $count")
        }

        LaunchedEffect(count) {
            Log.d("clwater", "LaunchedEffect(count)")
        }
        DisposableEffect(count) {
            Log.d("clwater", "DisposableEffect(count) out onDispose")
            onDispose {
                Log.d("clwater", "DisposableEffect(count) in  onDispose")
            }
        }
    }

    @Composable
    fun TestLifecycleCompose() {
        var show by remember {
            mutableStateOf(true)
        }

        Column {
            Button(onClick = {
                show = false
            }) {
                Text(text = "Hide")
            }

            if (show) {
                ControlCompose()
            }
        }
    }

Log:

2023-06-07 14:12:20.443 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(Unit) out onDispose
2023-06-07 14:12:20.443 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:20.503 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(Unit)
2023-06-07 14:12:20.504 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)


2023-06-07 14:12:27.923 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:27.923 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:27.929 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:28.309 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:28.310 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:28.314 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:28.608 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:28.609 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:28.614 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)


2023-06-07 14:12:32.043 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:32.044 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(Unit) in  onDispose

通过Log我们可以看出, DisposableEffect和LaunchedEffect一样, 在监听统一个值的变化的时候表现基本一致. 但是DisposableEffect却优先于LaunchedEffect触发(这里的话只能通过Log得到此结论, 得到这个结论的时候我也有点疑惑, 可能是Compose state导致性能开销大所以总慢一点? 查找相关文章的时候也没有提及的相关内容.)

当然, 我们还可以看到LaunchedEffect(Unit)只在进入组合时触发, DisposableEffect(Unit)中的onDispose 也只在退出组合时触发. 如果你想监听组件的Lifecycle, 不妨通过这两个位置来实现.

SideEffect:将 Compose 状态发布为非 Compose 代码

“如需与非 Compose 管理的对象共享 Compose 状态,请使用 SideEffect 可组合项,因为每次成功重组时都会调用该可组合项。”

关于SideEffect, 可以将其理解为非Compose代码的LaunchedEffect(Unit), 而且其在每次重组成功时调用.

其理解和实际使用都有点困难, 虽然其一般被建议在来组合生命周期无关功能中使用. 但是也没有发现在这种情况下的不可替代性.

不过, 从如需与非 Compose 管理的对象共享 Compose 状态这里, 我们可以将其理解为可以将SideEffect{}内的元素/功能/代码变为当前作用域下重组的参考.

我们先来看一下以下的代码

    @Composable
    fun TestLifecycleCompose() {
        var text by remember { mutableStateOf("Common") }
        Text(text = "text $text")
        Thread.sleep(3 * 1000)
        text = "Delay text"
    }

你认为, 3秒后Text中的内容会变化么? 实际上不会的.

但是当我们加入SideEffect之后

    @Composable
    fun TestLifecycleCompose() {
        var text by remember { mutableStateOf("Common") }
        Text(text = "text $text")
        SideEffect {
            Thread.sleep(3 * 1000)
            text = "Delay text"
        }
    }

我们可以发现其在3s后, Text中的内容后发生变化.

(关于发生这样区别的原因, 以下均为我个人理解与想法, 重组这个动作更注重与对观察变量"读"的变化, 没有 SideEffect的时候, Text"读""text"的动作没有变化, 所以不发生重组. 而加入SideEffect后, 将"text"的中"读"的操作进行了触发, 最终引起了重组)

同样的, 我们在官网中还可以看到

  • produceState:将非 Compose 状态转换为 Compose 状态
  • derivedStateOf:将一个或多个状态对象转换为其他状态
  • snapshotFlow:将 Compose 的 State 转换为 Flow

篇幅有限, 这次就不能过多的介绍了.

最后

为了帮助大家更好的熟知 Jetpack Compose 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……

Jetpack 全家桶(Compose)

Jetpack 部分

  1. Jetpack之Lifecycle
  2. Jetpack之ViewModel
  3. Jetpack之DataBinding
  4. Jetpack之Navigation
  5. Jetpack之LiveData

Compose 部分
1.Jetpack Compose入门详解
2.Compose学习笔记
3.Compose 动画使用详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/908688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码审计-审计工具介绍-DAST+SAST+IAST项目

DASTSASTIAST项目介绍 DAST: 动态应用程序安全测试(Dynamic Application Security Testing)技术在测试或运行阶段分析应用程序的动态运行状态。它模拟黑客行为对应用程序进行动态攻击,分析应用程序的反应,从而确定该We…

*看门狗2_思考多组看门狗对多任务的监控

多任务系统中 一般结合嵌入式操作系统,设置一个优先级级别最高的任务作为监视器,以监视各个应用任务是否正常运行,该监视器即为软件看门狗,该任务对其他任务都设定一个计时器,每个被监视的任务在设定的时间内对软件看门…

企业级私有化部署即时通讯,完美替代SaaS平台

在数字化转型的时代,企业越来越需要安全、高效和可扩展的解决方案来管理其运营。作为通用办公行业的领军品牌,WorkPlus在企业级私有化部署领域成为改变者。凭借其尖端功能和全面的功能性,WorkPlus提供了传统的软件即服务(SaaS&…

SELinux 入门 pt.1

哈喽大家好,我是咸鱼 文章《SELinux 导致 Keepalived 检测脚本无法执行》以【keepalived 无法执行检测脚本】为案例向大家简单介绍了关于 SELinux 的一些概念 比如说什么是自主访问控制 DAC 和 强制访问控制 MAC;SELinux 安全上下文的概念等等 那么今…

洛谷 P2715 ccj与zrz之在回家的路上 艰辛的解题过程

时隔将近两个月,我回来了哈哈哈哈哈哈哈哈哈。 看着一周小结的排名越来越低太难受了,于是我回来继续写文章 今天这道题其实不难,重点是说一下解题过程中遇到的麻烦和坑点 题目描述 给出一个算式(a*bc或a/bc或abc或a-bc&#xff0…

深入理解JMM和并发三大特性

并发和并行 并发和并行的目的都是为了使CPU的使用率最大化,这两个概念也是我们容易混淆的。 并行(Parallel) 并行是指在同一时刻,有多条指令在多个处理器上同时执行,因为并行要求程序能同时执行多个操作&#xff0c…

APP上线为什么要提前部署安全产品呢?

一般平台刚上线或者日活跃量比较高的时候,很容易成为攻击者的目标,服务器如果遭遇黑客攻击,资源耗尽会导致平台无法访问,业务也无法正常开展,服务器一旦触发黑洞机制,就会被拉进黑洞很长一段时间&#xff0…

MySQL下载安装配置

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

微信聊天记录词云制作

文章目录 一:聊天记录传输至电脑二:聊天记录破解三:聊天记录分析(1)字段含义(2)词频统计和词云制作(3)效果展示 一:聊天记录传输至电脑 在雷电模拟器中打开r…

独家|2023 Q2业内3D-NAND研发动态最全一览表

有粉丝私信,希望可以看到业内比较全的3D-NAND动态。经过小编的找寻,结合TechInsight的信息以及各家原厂官网消息,整理出2023 Q2业内3D-NAND研发动态最全一览表,供大家参考。 声明:本表格均为公开信息整理,如…

AI提示语-让每个人都能轻松使用 AI,提高 10 倍生产力

AI提示语简介 无需代码,连接模型快速构建AI应用。让每个人都能轻松使用 AI,提高 10 倍生产力。 AI提示语介绍 AI 提示语致力于为各类AI应用搭建、运行和推广的基础平台,让广大用户都能将AI的力量运用到实际的工作与生活场景中。 AI提示语…

国产SSD崛起!2023年发展趋势解析

随着科技的不断进步,SSD已经成为了现代计算机不可或缺的一部分。而在这个市场中,国产SSD也逐渐崭露头角。 国产SSD市场份额继续扩大 根据市场调研机构IDC的数据显示,2023年中国SSD市场份额排名前五的厂商中有四家是国内企业,分别是…

【第三阶段】kotlin语言的内置函数apply

1.普通方式输出一个字串的信息 fun main() {var info"Ktolin"//普通方式println("info的长度为${info.length}")println("info的最后一个字符${info[info.length-1]}")println("info全转换为大写${info.toLowerCase()}") }执行结果 …

Go 语言在 Windows 上的安装及配置

1. Go语言的下载 Golang官网:All releases - The Go Programming Language Golang中文网:Go下载 - Go语言中文网 - Golang中文社区 两个网站打开的内容只有语言不同而已,网站上清晰的标注了不同操作系统需要对应安装哪个版本,其中…

对类加载过程的通俗理解

开局一张图 一般来说,类加载分为:加载、验证、准备、解析、初始化 5个步骤。 各阶段略叙 1、加载 将.class文件加载进内存 2、验证 判断.class文件的格式是否正确 3、准备 为类的静态变量分配内存并设置初始值。只有b、c会在此阶段进行处理。 //…

移动端的帮助中心该如何设计?

随着移动互联网的发展,APP作为一种高效的营销工具,已经成为企业抢占移动端流量的重要手段。同时随着智能手机用户规模不断扩大,以及用户的消费逐渐向移动端转移,使得越来越多企业认识到 APP与其营销模式是成为互补的关系&#xf…

数据驱动未来:基于状态的维修在工业领域的前景

随着科技的不断进步,工业界正迎来一场革命,而其中的一颗明星是基于状态的维修(CBM)。这一技术正在改变工业维护的方式,通过实时数据分析和人工智能驱动,使维护从被动的、计划性的转变为主动的、预测性的。本…

基于多元宇宙算法优化的BP神经网络(预测应用) - 附代码

基于多元宇宙算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于多元宇宙算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.多元宇宙优化BP神经网络2.1 BP神经网络参数设置2.2 多元宇宙算法应用 4.测试结果:5…

3种清除logo的方法,使其干净整洁 自然无痕

信息爆炸的时代,我们每天都和图片打交道经常会遇到一些带有水印的图片。这些水印可能是品牌的标志或者是版权信息,但有时候它们会干扰到我们对图片的欣赏和使用。那么,怎么去掉图片logo水印呢? 毕竟影响图片美感,使用也不方便&a…

torch一些操作

Pytorch文档 Pytorch 官方文档 https://pytorch.org/docs/stable/index.html pytorch 里的一些基础tensor操作讲的不错 https://blog.csdn.net/abc13526222160/category_8614343.html 关于pytorch的Broadcast,合并与分割,数学运算,属性统计以及高阶操作 https://blog.csd…