In this code (see thread), when a button is presse...
# compose
r
In this code (see thread), when a button is pressed, the message "Key() composed ..." prints four times, one for each Key. I wasn't expecting anything to print at all, since Key doesn't read "buttonPressed".
r
I know I'm sorry, I read that earlier and tried to do that but I'm a noob at Slack too šŸ˜ž
Copy code
@Composable
fun RecomposeTest() {
    var buttonPressed by remember { mutableStateOf(0) }
    Row {
        for (i in 1 until 5)
            Key(i, { buttonPressed = it} )
        Text(text = "$buttonPressed")
    }
}

@Composable
fun Key(
    index: Int,
    onClick: (index: Int) -> Unit
) {
    println("Key() composed, index: $index")
    Button(onClick = { onClick(index) }) {
        Text(text = index.toString())
    }
}
z
i’m guessing this is because you’re passing a lambda, and the lambda is getting re-allocated every time so it counts as a different parameter to
Key
which triggers the recomposition
r
Your hunch seems right because this (replace lambda with function) solves the "problem":
Copy code
@Composable
fun RecomposeTest() {
    var buttonPressed by remember { mutableStateOf(0) }
    fun onClick(index: Int) { buttonPressed = index}

    Row {
        for (i in 1 until 5)
            Key(i, ::onClick)
        Text(text = "$buttonPressed")
    }
}
What you cite though -- is it an implementation detail, or something that should be a part of a user's mental model?
a
cc @Leland Richardson [G]
r
With alpha12 this works as expected. I had not filed an issue. Was there a specific bug that was fixed in alpha 12, or was it a side effect of another fix? Browsing through the commit descriptions nothing jumps out at me.
z
I don’t know, but there was a compiler change in this release I think that removed emptyContent() because it no longer provided a performance optimization. I think the compiler became smart enough to not reallocate lambdas in some cases? If that’s true, it could explain why your original code started working.
l
we have improved the memoization strategy of lambdas a bit recently, though i would have expected your first code snippet to have correctly skipped for a while now (~6 releases or so)
What you cite though -- is it an implementation detail, or something that should be a part of a user’s mental model?
Generally speaking you shouldn’t have your code rely on a function skipping for correctness. Skipping should ideally only impact performance. If you have side effects that you want to ensure only get run ā€œonceā€ or something like that, you should use *effect APIs, and not execute those side effects during composition. Compose doesn’t guarantee skipping if the parameters are the same. There are conditions where we may execute anyway.
r
Thanks for the reply. I definitely would not depend on skipping. I was just trying to get the big picture of what causes recomposition. It sounds though that it's not a simple answer if Compose has its "internals" reasons.
l
no problem. in the example you originally posted, the composable is definitely skippable, but it’s important to remember that a reallocated lambda might be the reason for a skip-miss. We attempt to automatically memoize lambdas in cases where we know it is safe to do so but fairly simple code can end up de-optimizing out of this case. The rule of thumb algorithm is that we will memoize the allocation of a lambda inside of a composable scope if all of their capture objects are ā€œstableā€, and all of their capture objects compared equal.