Is there a way to remember the output of a composa...
# compose
a
Is there a way to remember the output of a composable such that it is only recomposed when its internal (remember) state changes? My scenario is that I have a box with some variable content and a context menu on it. The items in the context menu depend on the content and also some of the items modify the content. When I press a context item that modifies the content, the context menu disappears only after a short period (this is just how
DropdownMenu
behaves), but during that time, because the box content changed, the menu items are recomposed according to the new content.
Copy code
fun main() = singleWindowApplication {
    var flag by remember { mutableStateOf(false) }
    val dropdownMenuState = remember { DropdownMenuState() }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .contextMenuOpenDetector(dropdownMenuState)
    ) {
        DropdownMenu(
            state = dropdownMenuState,
            modifier = Modifier
                .background(Color.White)
        ) {
            @Composable
            fun menuItem(text: String, onClick: () -> Unit) {
                DropdownMenuItem(
                    onClick = {
                        onClick()
                        dropdownMenuState.status = DropdownMenuState.Status.Closed
                    }
                ) {
                    Text(text)
                }
            }

            if (flag) {
                menuItem("Item 1") { flag = false }
                menuItem("Item 2") { flag = false }
                menuItem("Item 3") { flag = false }
                menuItem("Item 4") { flag = false }
                menuItem("Item 5") { flag = false }
            }
            else {
                menuItem("Item 1") { flag = true }
            }
        }
    }
}
s
You can read the state in a different scope and control the data your menu renders there (e.g. only update contents when the menu is opened) I don't think there's a way to freeze content like this reliably. It is a big problem for removal animations as well, e.g. in lazy lists. Our current (still in development) strategy is to just capture a "screenshot"-like frozen node and animate it out.
Originally we experimented with freezing composition as well, but measure and draw are also observable scopes that read states and many modifiers have assumptions about composition happening before measure/draw in cases like these.
a
Can’t just take a screenshot because of the ripple animation when clicking.
s
Yeah, I think our current strategy is to freeze drawing updates as well, not sure ripple is going to be frozen tho.
It is still pretty much work in progress for now
a
I think there’s no choice here but to have the user remember the “external” state. But we need to have an API that makes this easier (and even to just make the user understand he needs to do this). Something similar to
drawWithCache
maybe.
s
I guess merging status and contents into a single data class is one way of doing it maybe?
So that you'll have to update them simultaneously
a
Ah, AnimatedContent handles this by having the user pass the external state to it. It then remembers it, and calls the content lambda with that state even after the “outside” value changed.
r
I think the only way to fight it is to use something like
delay(200)
. In a perfect world
DropdownMenuState
would have a suspending function
suspend fun animateToStatus(status: DropdownMenuState.Status)
and you could suspend until the animation is done and then change the underlying
flag
a
That’s also an interesting idea.