Lesson 4
SubcomposeLayout turns slots into a reuse pool
Read the active, reusable, and precomposed child ranges, then follow paused precomposition as a handle workflow.
Evidence legend: Durable marks a stable mental model; Version-specific marks behavior tied to the AndroidX pin below; Experimental marks a changing or transitional path.
SubcomposeLayoutState is not just a map from IDs to lambdas. At the pinned source, LayoutNodeSubcompositionsState keeps virtual child LayoutNodes and a NodeState for each composition. Its child list has exactly three storage ranges: active children, a reusable middle section, and precomposed children waiting at the end (SubcomposeLayout.kt#L506-L568). Paused is not a fourth range; it describes an incremental precomposition workflow/handle whose slot is still represented in the precomposed range. This is the storage model behind lazy-layout prefetch and slot reuse.
Active, reusable, and precomposed
During a measure pass, subcompose(slotId, content) first looks in slotIdToNode. If the ID is not active, it takes a matching node from precomposeMap, then tries takeNodeFromReusables, and only then creates a new node. It moves the chosen node to the current active index and composes it if the content changed, the composition has invalidations, or reuse forced a fresh pass (SubcomposeLayout.kt#L583-L624, SubcomposeLayout.kt#L635-L719). The slot ID chooses the child slot; it does not by itself preserve every remembered value when content is reused.
When a pass ends, disposeOrReuseStartingFromIndex constructs the available slot-ID set and asks SubcomposeSlotReusePolicy which IDs to retain. Retained nodes enter the reusable section and their compositions are reset for reuse; unretained nodes are disposed. When a future ID asks for a node, the implementation first searches for an exact previous ID, then asks areCompatible(newId, reusableId) for a compatible candidate (SubcomposeLayout.kt#L734-L828, SubcomposeLayout.kt#L871-L923). A policy is therefore a bounded reuse decision, not a promise to keep all off-screen content alive.
The pinned tests expose the controls: slotsKeptForReuse keeps two old slots and asserts deactivation; newSlotIsUsingReusedSlot adds an ID and checks that reuse is consumed; nothingIsReusedWhenMaxSlotsAre0 confirms zero retention disposes old slots (SubcomposeLayoutTest.kt#L892-L924, SubcomposeLayoutTest.kt#L1050-L1088). The bounded-retention evidence here is UI-specific: the Compose UI release notes describe SubcomposeLayoutState retaining slots and lazy rows/columns keeping previously visible items for reuse (Compose UI 1.0.0-beta07). The pinned UI source and tests define the current ranges and policy behavior. Compose Runtime 1.6.0-alpha03 notes are reusable-node context, not evidence for this SubcomposeLayout pool.
precompose(slotId, content) moves a reusable node to the precomposed range or creates one, then composes it before a later measure call. The returned PrecomposedSlotHandle can dispose the still-precomposed slot or premeasure its placeables; if regular subcompose has already claimed the slot, dispose does nothing (SubcomposeLayout.kt#L1014-L1040, SubcomposeLayout.kt#L328-L369). The precompose test verifies composition happens before the slot is needed during measure; disposePrecomposedItem verifies the handle ends its effect (SubcomposeLayoutTest.kt#L660-L723).
Paused precomposition: a pinned, narrow path
Version-specific
The pinned source exposes createPausedPrecomposition. Its handle can resume(shouldPause), report isComplete, then apply or cancel; the API describes incremental work between frames and the implementation routes it through precomposePaused (SubcomposeLayout.kt#L246-L326, SubcomposeLayout.kt#L1185-L1210). The pinned precomposePaused_composeAndApply, precomposePaused_isComplete, and precomposePaused_applyOnNotCompletedPrecompositionThrows tests prove resume/complete/apply sequencing (SubcomposeLayoutTest.kt#L3276-L3470). Keep this claim scoped to the pinned API and tests, not every prefetcher.
Reproduce the boundary
At the pinned AndroidX checkout with an attached device or emulator, run:
./gradlew :compose:ui:ui:connectedAndroidTest \
-Pandroidx.testInstrumentationRunnerArguments.class=androidx.compose.ui.layout.SubcomposeLayoutTest
Controls: compare zero/bounded retention, consume a precomposed slot during measure, and call apply before and after completion. Expected: retained nodes are deactivated rather than immediately disposed, precomposition removes later composition work, and incomplete paused work rejects apply. Limits: device timing and platform setup are required; this does not prove a particular lazy list’s policy. This procedure was not executed in this content edit.
Check yourself
Given active A, B, reusable C, D, and precomposed E, a request for E claims the precomposed node; a compatible new ID searches reusable; zero retention disposes. A precomposed handle may be paused, but that workflow state is not another range and is not applicable merely because resume was called: check completion before apply.
Evidence ledger
| Claim | Evidence | Label |
|---|---|---|
| Child nodes are partitioned into active, reusable, and precomposed ranges | SubcomposeLayout.kt#L535-L568 | Version-specific |
| Policy and compatibility decide reuse versus disposal | SubcomposeLayout.kt#L734-L923 | Version-specific |
| Paused precomposition requires completion before apply | SubcomposeLayout.kt#L246-L326, SubcomposeLayoutTest.kt#L3441-L3470 | Version-specific |
Misconceptions
- “A slot ID is a slot-table address.” It matches child slots within one
SubcomposeLayoutState; it is not a global composition identity. - “Precomposition immediately inserts visible nodes.” It composes a slot ahead of demand; ordinary
subcomposestill claims it for the active range. - “Calling
resumemakes paused work applyable.” The handle must report completion beforeapply; incomplete work follows the pinned precondition.
Freshness
Refresh when active/reusable/precomposed range management, slot-reuse policy, precomposition handles, or paused-precomposition completion rules change. Keep the paused path scoped to the pinned SubcomposeLayoutState implementation and tests.
Finished this lesson?
Your progress stays only in this browser.