Jetpack Compose – 无重组,为什么?

huangapple go评论65阅读模式
英文:

Jetpack Compose - no recomposition, why?

问题

我很困惑,因为我认为如果状态发生变化或者组合内容发生更改,比如按钮的文本或大小,它会触发对该组合内容的重新组合。

在我的示例中,有一个名为 timerForCompose() 的挂起函数,它在一定延迟后打印消息:"计时器结束"。如果协程停止,它应该打印"计时器取消"。这个挂起函数在 Timer() 的组合函数中调用,并将在 LaunchedEffect-Composable 的 lambda 中执行。

我的 MainComposable 函数叫做 TimerScreen1,包括一个按钮。在应用启动或重新组合时,按钮的大小会发生变化,并且 Timer() 会接收到由状态对象 timerDuration 提供的初始延迟时间。

以下是代码部分:

挂起函数:

suspend fun timerForCompose(time: Long, onTimerEnd: () -> Unit) {
    delay(timeMillis = time)
    onTimerEnd()
}

计时器 Composable 函数:

@Composable
fun Timer(timerDuration: Long) {
    LaunchedEffect(key1 = Unit) {
        try {
            timerForCompose(timerDuration) {
                println("计时器成功结束")
            }
        } catch (ex: Exception) {
            println("计时器已取消")
        }
    }
}

TimerScreen1 函数:

@Composable
fun TimerScreen1() {
    var dpForButton = remember {
        mutableStateOf(200.dp)
    }

    var timerDuration = remember {
        mutableStateOf(4000L)
    }

    Button(
        onClick = { dpForButton.value = 400.dp },
        modifier = Modifier.size(dpForButton.value)
    ) {
        Text(timerDuration.value.toString())
        Timer(timerDuration = timerDuration.value + 2000L)
    }
}

问题在于,我不理解为什么每次点击按钮时 ButtonComposable 都不会重新组合。对我来说,如果我点击按钮,整个按钮应该重新组合,这样 Timer() 函数也会再次调用。但这并没有发生。只有在旋转屏幕时才会重新组合。你可能会想知道为什么 timerDuration 有一个状态。这是因为最初的代码就像这样:

原始代码链接

我只是尝试将其简化到了这个按钮,因为我期望会进行重新组合。

英文:

im very confused now, because i thought that if a state changes or a composable is altered, like the text of a button, or the size, it would initiate a recomposition of that composable.

In my example there is a suspendfunction timerForCompose() which prints after a certain delay the message: "Timer ended". If the coroutine did stop it should print "Timer cancelled". The suspendfunction is called in the composableFunction Timer(), and will be executed in the lambda of the LaunchedEffect-Composable.

My MainComposablefunction is called TimerScreen1 and consits of a Button. On AppStart or Recomposition the size of the button changes and Timer() receives the initial delay time, provided by the state object timerDuration.

Here is the code:

The supend-function:

suspend fun timerForCompose(time: Long, onTimerEnd: () -> Unit){
delay(timeMillis = time)
onTimerEnd()
}

Timer Composable Function

@Composable
fun Timer(timerDuration: Long) {

LaunchedEffect(key1 = Unit){

    try {
        timerForCompose(timerDuration){
            println("Timer ended successfully")
        }
    }
    catch (ex: Exception){
        println("Timer canceled")
    }}  }

TimerScreen1 function:

@Composable

fun TimerScreen1() {

var dpForButton = remember {
    mutableStateOf(200.dp)
}

var timerDuration = remember {
    mutableLongStateOf(4000L)
}

    Button(onClick = { dpForButton.value = 400.dp }, modifier = Modifier.size(dpForButton.value)) {
        Text(timerDuration.value.toString())
        Timer(timerDuration = timerDuration.value + 2000L)

    } }

The problem: I dont understand why the ButtonComposable is not recomposing every time i click on the button. For me it would be logical that if i click, the whole Button gets recomposed and with that Timer()-function would be called again. But this does not happen.
It only recomposes if i rotate the screen. You might be wondering why timerDuration has a state. Its because the initial Code was like here:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-html -->

https://betterprogramming.pub/jetpack-compose-side-effects-launchedeffect-with-example-99c2f51ff463

<!-- end snippet -->

I just tried to break it down to that button, cause i expected a recomposition.

答案1

得分: 0

MutableState文档中提到:

> 当写入并更改value属性时,将计划重新组合任何已订阅的RecomposeScope。如果使用相同的值写入value,则不会计划重新组合。

所以问题在于 - 您总是将相同的值设置给dpForButton,因此不会计划重新组合。


另一个问题是,您在LaunchedEffect内部使用key = Unit调用timerForCompose,这意味着该效果内部的代码仅在首次进入组合时运行。如果希望每次重新组合时都运行它,您应该使用SideEffect

英文:

The documentation of MutableState says:

> When the value property is written to and changed, a recomposition of any subscribed RecomposeScopes will be scheduled. If value is written to with the same value, no recompositions will be scheduled.

So this is the problem - you always set the same value to dpForButton, so recomposition is not scheduled.


Another problem is that you are calling timerForCompose inside of LaunchedEffect with key = Unit, which means that the code inside of that effect will only run the first time it enters composition. If you want to run it every time it recomposes, you should use SideEffect.

huangapple
  • 本文由 发表于 2023年7月24日 17:50:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76753269.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定