SubcomposeLayout and Subcomposition During the Measure Pass
SubcomposeLayout and Subcomposition During the Measure Pass
At a glance, SubcomposeLayout looks like another Layout. You give it a modifier and a measure policy, and it returns a node in your UI tree. The difference hides in one method on its measure scope: subcompose. Calling it does not measure children that already exist. It composes them, during the measure pass, into a composition that SubcomposeLayout owns. That single capability is why it is a heavier tool than Layout or Box, and why reaching for it without a reason costs more than you expect. By the end of this lesson, you will be able to:
- Explain why
SubcomposeLayoutcomposes during the measure pass and whatsubcomposebuilds for each slot. - Describe how owning a subcomposition differs from emitting children into a regular
Layout. - Identify the runtime costs: a composition and slot table per slot, composition folded into measurement, and recomposition that can escalate to remeasure.
- Compare
BoxwithBoxWithConstraintsto see that cost in a component you use every day. - Decide when
SubcomposeLayoutis the right tool and when a plainLayout,Modifier.layout, oronSizeChangedis enough.
It looks like a layout, but it owns a composition
In a normal layout, composition and measurement are separate steps. Composable functions run first, during the composition phase, and produce a node tree. Only then does Layout measure and place the children it was handed. The children already exist by the time measurement starts, and the measure policy just decides their size and position.
SubcomposeLayout breaks that separation on purpose. Its measure scope adds one method:
interface SubcomposeMeasureScope : MeasureScope {
fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable>
}
The signature is the whole story. You pass in a @Composable lambda and get back List<Measurable>, which means the content was composed on the spot, inside the measure pass, and is now ready to measure. A regular Layout consumes children that someone else composed. SubcomposeLayout produces them itself.
That ability exists for one reason: some content cannot be decided until measurement. BoxWithConstraints needs the incoming constraints to choose between a compact and an expanded layout. A lazy list needs the viewport height to know how many items to compose. The constraints only exist during measurement, so the composition that depends on them has to happen there too. The cost discussed in the rest of this lesson is the price of that ability, so the practical question is never "how does it work" alone, but "is this content actually constraints dependent enough to pay for it."
What subcompose allocates for each slot
The reason SubcomposeLayout is not just another Layout is what it builds the first time you call subcompose. Each distinct slotId gets its own set of runtime objects. Tracing a fresh slot, the state first creates a virtual LayoutNode as a child of the root:
private fun createNodeAt(index: Int) =
LayoutNode(isVirtual = true).also { node ->
ignoreRemeasureRequests { root.insertAt(index, node) }
}
Alongside the node, it keeps a NodeState, and the NodeState holds the slot's composition:
private class NodeState(
var slotId: Any?,
var content: @Composable () -> Unit,
var composition: ReusableComposition? = null,
)
The composition itself is a real one, built by createSubcomposition:
internal fun createSubcomposition(
container: LayoutNode,
parent: CompositionContext,
): ReusableComposition = ReusableComposition(createApplier(container), parent)
Each ReusableComposition is a CompositionImpl, and every CompositionImpl allocates its own slot table to track its recomposition state. So one subcompose call stands up a virtual node, a NodeState, a Composition, and a slot table. Three slot ids mean three of each.
A plain Layout allocates none of this. Its children are composed once, inline, into the parent composition's single slot table, and its measure policy measures children that are already there. There is no per slot Composition, no extra slot table, and no slot bookkeeping object. This is the concrete meaning of "subcomposition": SubcomposeLayout runs a small fleet of child compositions that the parent layout drives by hand, and that fleet is pure overhead a regular layout never carries.
This interview continues for subscribers
Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.
Become a Sponsor