Lesson 2
Reusable composition changes the node lifecycle
Distinguish node recycling from movable identity and follow reuse, deactivation, and release callbacks.
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.
Movable content answers “how do I keep this identity while its location changes?” Reuse answers “how do I avoid allocating a new compatible node for different content?” The second question deliberately has a different answer for remembered values: node structure may be recycled, while remembered slots are treated as invalid for the new content.
Two reusable layers
ReusableContent(key) { ... } starts a reusable group. When key changes, cached values act invalid, changed behaves as changed, and the composer seeks ReusableComposeNode nodes to update instead of replacing (Composables.kt#L130-L146, Composer.kt#L500-L520). ReusableComposeNode marks this protocol with startReusableNode() before factory/update work (Composables.kt#L318-L337). Ordinary ComposeNode is not automatically recyclable; a non-reusable node forces replacement for itself and its children.
ReusableContentHost(active, content) is the lifetime gate. When inactive, it calls deactivateToEndGroup: cached slot values are cleared while applier-managed nodes remain in place (Composables.kt#L149-L174, Composer.kt#L435-L445). Reactivation composes again and makes reusable nodes candidates. Deactivation is not a hidden remember extension: it forgets remembered data and observations while keeping a physical node available.
The public ReusableComposition interface gives a child-composition owner the same distinction. setContentWithReuse discards remembered content and reuses eligible nodes when group structure matches; deactivate removes observation scopes and remembered slots while preserving nodes (Composition.kt#L92-L121). Normal Composition.dispose() ends the composition and releases its hierarchy. Runtime release history places ReusableContent/ReusableComposeNode and later ReusableComposition.deactivate in separate additions (Compose Runtime 1.6.0-alpha03, 1.6.0-alpha04).
Lifecycle callbacks are not remember callbacks
Nodes implementing ComposeNodeLifecycleCallback can observe three intermediate states. onReuse resets data that belonged to prior content; onDeactivate releases resources that depend on current composition use while leaving the node eligible for later reuse; onRelease is terminal for that node (ComposeNodeLifecycleCallback.kt#L30-L60). RememberObserver is insufficient for data associated with a recycled node because deactivated or reused remembered parts are disposed while the node remains available.
The pinned CompositionReusingTests make the split observable: canReuse asserts identical node objects but a different remembered object after a key change; reusableContentHostCanDeactivate observes remembered state forgotten while inactive; onReleaseIsNotCalledOnReuse and onReleaseIsNotCalledWithMovableContentMovement keep recycling and moving separate from terminal release (CompositionReusingTests.kt#L44-L110, CompositionReusingTests.kt#L207-L263, CompositionReusingTests.kt#L529-L645).
| Mechanism | Primary promise | Remembered values | Physical nodes |
|---|---|---|---|
movableContentOf | Preserve one content identity at a new call site | Transfer with the content | Transfer with the content |
ReusableContent | Recycle compatible node groups for a changed key | Treat as invalid/new | Reuse eligible nodes |
ReusableContentHost(false) | Temporarily deactivate a reusable region | Forget observations and slots | Keep nodes in place |
dispose / ordinary removal | End ownership | Release | Remove/release |
Reproduce the boundary
At the pinned AndroidX checkout, run:
./gradlew :compose:runtime:runtime:desktopTest \
--tests 'androidx.compose.runtime.CompositionReusingTests.canReuse' \
--tests 'androidx.compose.runtime.CompositionReusingTests.reusableContentHostCanDeactivate' \
--tests 'androidx.compose.runtime.CompositionReusingTests.onReleaseIsNotCalledOnReuse' \
--tests 'androidx.compose.runtime.CompositionReusingTests.onReleaseIsNotCalledWithMovableContentMovement'
Controls: change a reusable key, toggle host activity, remove content, and move the same movable content. Expected: reuse keeps eligible node identity but not the remembered object; deactivation forgets state without release; removal eventually invokes release. Limit: runtime mock tests do not define a UI toolkit’s resource reset or layout policy. This procedure was not executed in this content edit.
Check yourself
A ReusableContent key changes while its group emits one ReusableComposeNode and one ordinary node. Predict the result: the reusable node is a candidate for update, the ordinary node is replaced, and remembered values are not a cache that survives the recycling transition. If the requirement instead says “the exact remembered editor follows a new branch,” use movable content, not reusable content.
Evidence ledger
| Claim | Evidence | Label |
|---|---|---|
| Reusable groups invalidate cached values but seek reusable nodes | Composer.kt#L466-L520 | Version-specific |
| Deactivation preserves nodes while clearing remembered slots | Composition.kt#L97-L121 | Version-specific |
| Tests distinguish reuse, deactivate, move, and release | CompositionReusingTests.kt#L529-L645 | Version-specific |
Misconceptions
- “Reusable content preserves remembered state.” Reuse targets eligible physical nodes; the remembered slots are treated as invalid for the new content.
- “Deactivation releases the node.” Deactivation forgets composition-owned values and observations while leaving an eligible node in place; release is terminal.
- “Movable content and reusable content solve the same problem.” Movable content transfers one identity; reuse recycles a compatible node for different content.
Freshness
Refresh when reusable-group entry, deactivation, ComposeNodeLifecycleCallback, ReusableComposition, or release semantics change. Keep node reuse separate from remembered-value lifetime and movable-content transfer.
Finished this lesson?
Your progress stays only in this browser.