Lesson 5

From a node operation to LayoutNode

Connect compiler-facing node operations to the Compose UI tree without equating every composable with a node.

3 min readUpdated Jul 11, 2026

Status: The distinction between group calls and node calls is Durable . ComposeUiNode and LayoutNode details are Version-specific at the pinned revision.

Outcome

You should be able to trace one target-node insertion and explain why a wrapper composable can run without inserting anything.

Group work is not node work

The Composer protocol has both group operations and node operations. A group records logical composition structure. A node operation asks the applier-owned tree to create, retain, or update a target object.

This distinction matters because many useful composables do not emit nodes. A wrapper may provide a scope. A state read may register an observation. A control-flow branch may change groups. None of those facts alone means that a new UI object exists.

Diagram
flowchart LR
    NodeCall[ComposeNode call] --> Record[Recorded node operation]
    Record --> Applier[UI applier]
    Applier --> Layout[LayoutNode]
    Layout --> Later[Measure and draw later]

The final box is deliberately brief. Measurement and drawing are downstream UI work, not Chapter 1’s subject.

The Compose UI bridge

The generic runtime only knows an applier and a node type. Compose UI supplies ComposeUiNode. Its constructor points to LayoutNode.Constructor, and its update properties include values such as the modifier, measure policy, density, and composition locals.

That gives this concrete path:

  1. Compiler-generated code invokes the runtime node protocol.
  2. Composition records a create or update operation.
  3. applyChanges sends the operation to the Compose UI applier.
  4. The applier inserts or updates a LayoutNode.
  5. Later UI phases decide whether measurement, placement, or drawing work is needed.

Steps 1–3 are runtime protocol boundaries. Step 4 is the Compose UI bridge. Step 5 belongs to downstream UI internals.

Insert, retain, update

On first composition, a node-producing call can create and insert a node. On a later execution, the runtime can retain that node and apply a property update instead. The node object need not be recreated just because the composable body was considered again.

The pinned LayoutNode.kt exposes child insertion, removal, and movement. Those methods are target-tree operations. They are not evidence that the slot storage and node tree are the same structure.

A logging-applier exercise

Use a small LogNode tree and an AbstractApplier<LogNode>. Log these methods:

  • insertTopDown;
  • remove;
  • move; and
  • the property block used by a node update.

Compose one node with label first, apply changes, then change only its label and apply again. The useful observation is one insertion and two property applications, not a second insertion. A real test should assert tree state and operation counts. Log text is only a debugging aid.

Check yourself

A composable calls Marker(), which reads state but never calls ComposeNode. Which operations can still change? Which operation should you not assume?

Source notes

ClaimDirect evidenceStatus
The runtime exposes separate group and node operationsComposer.kt, startNode, createNode, and useNodeDurable Version-specific
Compose UI supplies the ComposeUiNode bridgeComposeUiNode.ktVersion-specific
LayoutNode owns concrete child operationsLayoutNode.kt, insertAt, removeAt, and moveVersion-specific

Freshness

Refresh this lesson when Composer node operations, ComposeUiNode, or LayoutNode child operations move. Keep measurement and drawing as downstream context unless the chapter boundary changes.

Finished this lesson?

Your progress stays only in this browser.