Lesson 1

Applier operations are a tree protocol

Read traversal, insertion, removal, movement, clearing, and change-list application as one target-tree protocol.

3 min readUpdated Jul 12, 2026

Status: The target-tree contract is Durable . The operation classes and execution order below are Version-specific at the pinned snapshot.

Outcome

Given a pending change, identify which object owns the mutation, where current points, and why a change list is not the UI tree.

current is a traversal cursor

An Applier<N> owns operations against a target tree. down(child) moves the cursor into a child; up() returns to its parent. insertTopDown, insertBottomUp, remove, move, and clear mutate children of the current node. apply(block, value) invokes a property block on current; the runtime does not know whether that property means text, a measure policy, or something else.

AbstractApplier supplies the stack behind current. Its clear() empties the stack, restores the root cursor, and delegates root cleanup to onClear() (Applier.kt). The pinned AbstractApplierTest checks nested down/up, root restoration after clear, multi-item removal, and the move-index rule (tests). A custom applier therefore has two jobs: maintain cursor invariants and mutate its own child representation.

A ChangeList is deferred work

ControlledComposition.recompose() calculates changes; applyChanges() is the boundary that performs them (Composition.kt). At the pinned implementation, the gap-buffer ChangeList stores operations and executes them against slot storage plus an applier (ChangeList.kt). The list is an intermediate program: it can contain UpdateNode, RemoveNode, MoveNode, traversal, and insertion fixups (Operation.kt).

Application brackets execution with onBeginChanges() and onEndChanges(). Remembered callbacks and side effects dispatch after the operation execution (Composition.kt). That is why “the composable ran” does not mean “the target tree changed.” The pinned contract says an application exception irreparably damages the composition, which should then be disposed (Composition.kt).

Choose one insertion direction

For a normal node insertion, the applier must implement either top-down or bottom-up insertion, not both. Top-down inserts a parent before its children; bottom-up builds children first. Choose the direction that avoids repeated ancestor or descendant notifications for your tree (Applier.kt). A special movable-content copy path is outside this normal insertion contract.

Reproduce the index invariant

At the pinned AndroidX checkout, run:

./gradlew :compose:runtime:runtime:desktopTest \
  --tests 'androidx.compose.runtime.AbstractApplierTest'

Controls: exercise move(1, 3, 1) on A B C D E, then remove one and several children. Expected: the final child list follows the pre-change to index rule, and clear() leaves current === root. Limit: this tests AbstractApplier helpers, not a Compose UI frame or every ChangeList ordering. Not executed in this production run.

Misconceptions

  • “A change list is already the rendered tree.” It is deferred operations plus slot-storage fixups.
  • move(from, to, count) uses the final destination index.” to is relative to the list before the change.
  • “A recomposition replaces every child.” The list can update, remove, or move existing nodes.

Check yourself

Before applyChanges(), which can already be different: composition memory, the pending operation list, or the applier’s target tree? Explain why.

Evidence ledger

ClaimDirect evidenceStatus
Applier owns traversal and target-tree operationsApplier.ktDurable
Composition calculates before applyChanges executesComposition.ktDurable Version-specific
Gap-buffer ChangeList executes operations against an applierChangeList.ktVersion-specific
AbstractApplier preserves cursor and list-helper behaviorApplier.kt and AbstractApplierTest.ktDurable Version-specific

Freshness

Refresh when Changes.execute, operation fixups, applier cursor semantics, or the applyChanges error contract changes. Do not infer UI measurement or drawing from this runtime-only lesson.

Finished this lesson?

Your progress stays only in this browser.