Android compose – 副作用混淆

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

Android compose - Side effects confusion

问题

In the official Google code, there is an example of some buggy Compos code that assumes a sequential execution model. They do not show an example of how to make this code not buggy.

To make this code safe for parallel, non-sequential execution without simply using items.size in the Text function call, you can indeed use LaunchedEffect to update itemCount. However, as you mentioned, the provided code still has the same bug. To fix it, you can use a mutableStateOf to ensure that the itemCount value is updated correctly and the Text function always shows the correct value. Here's an improved version:

@Composable
fun NotBuggy(
    items: List<String>
) {
    var itemCount by remember { mutableStateOf(0) }
    Row {
        Column {
            items.forEach { item ->
                Text("Item: $item")
                LaunchedEffect(Unit) {
                    itemCount++
                }
            }
        }
        Text("Count: $itemCount")
    }
}

By using mutableStateOf, you ensure that changes to itemCount trigger recomposition, and the Text function will always display the up-to-date count.

英文:

In the official google code, there is an example of some buggy Compos code, which is buggy because it assumes a sequential execution model:

@Composable
fun Buggy(
    items: List&lt;String&gt;
) {
    // Composabe functions can:
    // Execute in any order because they are assumed to be &quot;pure&quot; functions
    // Run in parallel
    // Whether or not you observe this happening is dependant on
    // your runtime environment and the compose runtime code
    // But *DO NOT* assume that because you have written code that
    // appears sequential that it will be executed sequentially...
    // For instance, the following code is not correct:

    var itemCount = 0
    Row {
        Column {
            items.forEach { item -&gt;
                Text(&quot;Item: $item&quot;)
                itemCount++ // Avoid! Side-effect of the column recomposing.
            }
        }
        // Inuitively, we expect that this will render the length of myList
        // But in reality, this code may run during (or even before!) the execution Column
        // Therefore, relying on side effects such as read/writes to some external state
        // And expecting execution to be sequential is a bug waiting to happen
        Text(&quot;Count: $itemCount&quot;)
    }
}

However, they do not show an example of how to make this code not buggy.

How should we go about making this code safe for a parallel, non sequential execution model without simple using items.size in the Text function call? I know this is contrived, but I want to understand the "correct" way to do a side effect like shown in the buggy example, that will cause Text to always show the "correct" value.

Should I use LaunchedEffect to update the itemCount like:

@Composable
fun NotBuggy(
    items: List&lt;String&gt;
) {
    var itemCount = 0
    Row {
        Column {
            items.forEach { item -&gt;
                Text(&quot;Item: $item&quot;)
                LaunchedEffect(Unit) {
                    itemCount++
                }
            }
        }
        Text(&quot;Count: $itemCount&quot;)
    }
}

Surely this would still have the same bug?

答案1

得分: 1

I believe that the purpose of this example is to encourage you to completely avoid side effects. All calculations should happen beforehand, and your composable function should receive everything required to render UI.

With count, it's easy to avoid because we can use items.count() and avoid any manual calculation.

Assume there is something more complex, and you want to display a summary that somehow depends on the list of items. In that case, the summary should be calculated beforehand, and the function receives it as an input.

@Composable
fun NotBuggy(
    items: List<String>,
    summary: String
) {
    Row {
        Column {
            items.forEach { item ->
                Text("Item: $item")
            }
        }
        Text(summary)
    }
}

LaunchedEffect is useful when you can't avoid side effects because this side effect is not for calculation but directly affects UI state. For example, showing a snackbar or scrolling the list.

However, sometimes side-effects are necessary, for example, to trigger a one-off event such as showing a snackbar or navigating to another screen given a certain state condition. These actions should be called from a controlled environment that is aware of the lifecycle of the composable.

You can find valid examples in the documentation: https://developer.android.com/jetpack/compose/side-effects#launchedeffect

英文:

I believe that purpose of this example is to encourage you to completely avoid side effects. All calculation should happen beforehand and your composable function should receive everything required to render UI.

With count it's easy to avoid, because we can use items.count() and avoid any manual calculation.

Assume you there is something more complex and you want to display a summary that somehow depend on the list of items. In that case summary should be calculated beforehand and function receives it as an input.

@Composable
fun NotBuggy(
    items: List&lt;String&gt;,
    summary: String
) {
    Row {
        Column {
            items.forEach { item -&gt;
                Text(&quot;Item: $item&quot;)
            }
        }
        Text(summary)
    }
}

LaunchedEffect is useful when you can't avoid side effect because this side effect is not for calculation, but directly affects UI state. For example, show a snackback or scroll the list.

>However, sometimes side-effects are necessary, for example, to trigger a one-off event such as showing a snackbar or navigate to another screen given a certain state condition. These actions should be called from a controlled environment that is aware of the lifecycle of the composable.

You can find valid examples in documentation: https://developer.android.com/jetpack/compose/side-effects#launchedeffect

huangapple
  • 本文由 发表于 2023年6月8日 17:27:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76430416.html
匿名

发表评论

匿名网友

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

确定