Lesson 6

Follow one state change through the pipeline

Use a controlled composition to distinguish invalidation, recalculation, and applied node updates.

3 min readUpdated Jul 11, 2026

Status: The separation between observed state, recomposition calculation, and application is Durable . The controlled API sequence below is Version-specific at the pinned revision.

Outcome

You should be able to predict which event changes during a state update:

  • the state object is written;
  • an observed scope is made eligible for recomposition;
  • composition calculates pending operations; or
  • the applier mutates the target tree.

These are related events. They are not one event called “recomposition.”

Start with a no-node control

Use two composables:

@Composable
fun Root(label: MutableState<String>) {
    Marker()
    Emitted(label.value)
}

@Composable
fun Marker() { /* reads no node-producing API */ }

Marker is a no-node control. This exact example demonstrates group participation and no node emission; because it reads no state, it does not demonstrate state observation. Emitted is the node-producing branch. The experiment needs a logging applier and a mutable state value.

Run the controlled sequence

The pinned ControlledComposition API makes the phases visible:

composition.composeContent { Root(state) }
composition.applyChanges()
println(composition.recompose())       // no write: false
state.value = "second"                 // write observed by Root
composition.recordModificationsOf(setOf(state))
println(composition.recompose())       // pending work: true
composition.applyChanges()              // send changes to the applier

The explicit recordModificationsOf call matters. This controlled handoff supplies the invalidation set. A changed function argument by itself is not evidence that a controlled composition has discovered invalidation.

What to observe

A logging LogApplier should show a shape like this:

MomentExpected observation
Initial applyChangesOne node insertion and the first property application
No-write recomposeNo pending work
State write plus handoffThe observed scope becomes pending
Invalidated recomposeWork is calculated, but not yet applied
Second applyChangesOne property update and no second insertion

Exact test-harness output can include framework lines. Assert operation counts and root.children, not log formatting or timing.

The causal map

Diagram
flowchart TD
    Write[State write] --> Invalidate[Observed scope invalidated]
    Invalidate --> Recompose[Recompose calculates]
    Recompose --> Pending[Pending change]
    Pending --> Apply[applyChanges]
    Apply --> Update[Applier updates node]

The diagram does not claim that every state write immediately runs a frame. Scheduling belongs to a later chapter. Here we are isolating the controlled composition boundary.

What this proves, and what it does not

This experiment supports a bounded claim: an observed state write can invalidate a scope; recompose() can calculate pending work; applyChanges() can perform the applier operation.

It does not prove a universal scheduling policy, a fixed compiler output shape, or a performance result. It also does not show that every recomposition inserts nodes. The no-node control exists to make that last boundary visible.

Check yourself

If the second run changes only a node property, where should the observable mutation appear? Which event should you not infer from that observation?

Source notes

ClaimDirect evidenceStatus
Controlled composition separates recompose and applyChangesComposition.kt, ControlledCompositionDurable Version-specific
Reads and writes connect composition scopes to invalidationComposition.kt, recordReadOf and recordWriteOfVersion-specific
Runtime tests cover bounded restart and skip behaviorRestartTests.ktVersion-specific

Freshness

Refresh this lesson when controlled invalidation, recompose, or applyChanges changes. Rerun the logging fixture at the new pinned revision; do not turn its output formatting into a contract.

Finished this lesson?

Your progress stays only in this browser.