https://kotlinlang.org logo
#compose
Title
# compose
r

Rick Regan

02/01/2021, 5:21 PM
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

Rick Regan

02/01/2021, 5:24 PM
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

Zach Klippenstein (he/him) [MOD]

02/01/2021, 6:15 PM
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

Rick Regan

02/01/2021, 7:37 PM
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

Adam Powell

02/01/2021, 7:47 PM
cc @Leland Richardson [G]
r

Rick Regan

02/13/2021, 10:34 PM
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

Zach Klippenstein (he/him) [MOD]

02/14/2021, 12:43 AM
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

Leland Richardson [G]

02/16/2021, 7:17 PM
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

Rick Regan

02/16/2021, 10:04 PM
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

Leland Richardson [G]

02/16/2021, 10:30 PM
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.