Related to :point_up:, I'm using `SubcomposeLayout...
# compose
r
Related to ☝️, I'm using
SubcomposeLayout
with a map, and calling
subcompose(key){ MyComposable(key, value) }
. The behaviour I'm seeing is that when one key leaves the layout,
subcompose
is re-executed for every key, which is definitely not what I want. Any idea what could be causing this?
The objects are effectively static, so I'm considering manually caching the measure results, but that seems dangerous
s
How do you know that subcompose re-executes? Does it run the composable lambda or something else?
r
Yeah, I put a println in the lambda
s
I think you might accidentally create a different instance of measure policy, which causes recomposition of
SubcomposeLayout
itself.
r
I'm passing
Copy code
{ constraints ->
        with(layout) {
            doLayout(constraints)
        }
    }
where
layout
is `remember`ed and
doLayout
is a method on it, so I wouldn't think it would re-create it
But I can remember the whole thing and see
s
Hard to say without looking at the whole composable. You can just
println(this)
from measure policy and compare hash codes.
👍 1
👍🏾 1
r
Hmm, nope, that wasn't it. And updating to use
derivedStateOf
from the other thread didn't help either
Here's where I use it
Copy code
val layout = remember {
    ViewLayout(
        derivedStateOf { staticObjectPositions + movingObjectPositions },
        derivedStateOf(structuralEqualityPolicy()) {
            (movingObjectPositions.keys + staticObjectPositions.keys).associateWith { updatedObjects.getValue(it) }
        },
        onClick
    )
}

SubcomposeLayout(
    remember { SubcomposeLayoutState(SubcomposeSlotReusePolicy(100)) },
    Modifier
        .fillMaxSize()
        .background(Color.Black)
        .zIndex(-10f)
        .onSizeChanged {
            with(density) {
                val size = it.toSize().let { DpSize(it.width.toInt().toDp(), it.height.toInt().toDp()) }
                if (size != viewportSize)
                    viewportSize = size
            }
        },
    remember {
        return@remember { constraints ->
            with(layout) {
                doLayout(constraints)
            }
        }
    }
)
and here's the class itself:
Copy code
@Stable
private data class ViewLayout(
    private val objectPositions: State<Map<UUID, DpOffset>>,
    private val visibleObjects: State<Map<UUID, SpaceObject<*>>>,
    private val onClickState: State<(UUID) -> Unit>
) {

    fun SubcomposeMeasureScope.doLayout(constraints: Constraints): MeasureResult {
        println("Remeasure $this@SpaceViewLayout")
        val placables = visibleObjects.value.mapValues { (id, obj) ->
            subcompose(id) {
                key(id) {
                    println("Item Compose")
                    SpaceObject(obj, {})
                }
            }
                .single()
                .measureMinIntrinsics(constraints)
        }

        return layout(constraints.maxWidth, constraints.maxHeight) {
            println("Relayout")
            placables.forEach { (id, placable) ->
                val offset = objectPositions.value[id] ?: return@forEach
                placable.place(offset.run { IntOffset(x.roundToPx(), y.roundToPx()) })
            }
        }
    }
}
The output as things exit the viewport looks like:
Copy code
Relayout
Remeasure androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope@8452c46@SpaceViewLayout
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Relayout
Remeasure androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope@8452c46@SpaceViewLayout
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Item Compose
Relayout
Remeasure androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope@8452c46@SpaceViewLayout
Item Compose
Item Compose
Item Compose
Item Compose
Relayout
Remeasure androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope@8452c46@SpaceViewLayout
Item Compose
Relayout
This is Compose for Desktop, fwiw
s
This one is a bit tricky, but the reason for recomposition is that you are subcomposing with a new lambda on each invocation.
You probably want to have
visibleObjects: Map<UUID, @Composable () -> Unit>
in this case
Technically it should skip in
SpaceObject
, so not sure it is that big of a deal 🙂
r
Ah interesting, I had expected the lambda definition would be pulled up
s
Yeah, but you capture
obj
from map
Also don't use
key
to wrap the item, it won't reuse it then. We have
ReusableContent
which does kinda the same thing, but I am not sure you need that.
r
Ah great, that worked. Thanks!