Lesson 6
Emit nodes from composables
Define a Compose target and connect declarative Atlas components to retained Atlas nodes.
Learning goal
Define a Compose target and connect declarative Atlas components to retained Atlas nodes.
From call to node
flowchart LR
Call["Text(Hello)"] --> Compiler[Compose compiler transformation]
Compiler --> CN[ComposeNode]
CN -->|new call-site identity| Factory[create TextNode]
CN -->|changed parameter| Update[record property update]
Factory --> Applier
Update --> Node[existing TextNode]Mark the custom target
A target marker lets the Compose compiler distinguish Atlas composables from composables intended for another Applier family.
package atlas.component
import androidx.compose.runtime.ComposableTargetMarker
@Retention(AnnotationRetention.BINARY)
@ComposableTargetMarker(description = "Atlas UI")
@Target(
AnnotationTarget.FILE,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.TYPE,
AnnotationTarget.TYPE_PARAMETER,
)
annotation class AtlasComposable
Target inference covers callers of already-targeted composables, such as Greeting. Explicit @AtlasComposable is required on functions that call ComposeNode directly, abstract composables, subcomposition lambdas, and stored composable lambda types; it is also useful for making public API boundaries clear.
A leaf component
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
private val DefaultTextColor = AtlasColor(0xFFFFFFFFu)
@Composable
@AtlasComposable
fun Text(
value: String,
color: AtlasColor = DefaultTextColor,
) {
ComposeNode<TextNode, AtlasApplier>(
factory = { TextNode(value, color) },
update = {
update(value) { text = it }
update(color) { this.color = it }
},
)
}
Because the factory initializes both properties, update avoids redundantly assigning them during insertion. Updater.set is the alternative when a property must also be assigned during insertion.
The property setters on TextNode decide whether a change invalidates layout or paint. Composables describe values; nodes own retained mutation policy.
A container component
@Composable
@AtlasComposable
fun Group(
content: @Composable @AtlasComposable () -> Unit,
) {
ComposeNode<GroupNode, AtlasApplier>(
factory = ::GroupNode,
update = {},
content = content,
)
}
A container opens a node group so emitted descendants become its children through the Applier.
flowchart TD
GroupCall[Group composable] --> GroupNode
GroupNode --> TextCall1[Text call site 1]
GroupNode --> TextCall2[Text call site 2]
TextCall1 --> TextNode1
TextCall2 --> TextNode2Public content type
Any host or component that stores Atlas content should preserve both annotations:
typealias AtlasContent = @Composable @AtlasComposable () -> Unit
This lets the compiler diagnose accidental calls between incompatible composable targets when it can determine the mismatch.
Never perform surface output here
flowchart LR
Composable -->|emit/update intent| Node
Node -->|later| Layout
Layout --> Paint
Paint --> Surface
Composable -. forbidden direct output .-> SurfaceComposable functions can rerun, be skipped, or have a recomposition discarded. Direct calls to surface.present inside Text would make output depend on composition execution details rather than committed tree state.
First content tree
@Composable
@AtlasComposable
fun Greeting() {
Group {
Text("Hello")
Text("new surface", color = AtlasColor(0xFF7DD3FCu))
}
}
After composition and apply, the retained result is:
RootNode
└── GroupNode
├── TextNode("Hello")
└── TextNode("new surface")
Visual checkpoint
Draw two paths from Text(value): one for first insertion and one for a later changed value. The first must pass through the factory and Applier; the second must end at the retained node’s property update.
Review questions
- Why does
Textemit a node rather than aSurfaceCommand? - When should an updater use
updaterather thanset? - What does the target marker protect?
Recap
@AtlasComposable identifies the Applier family.
ComposeNode connects a call site to a retained node.
Factories create; updaters change properties; the Applier changes structure.
Surface output still happens later.
Sources
Finished this lesson?
Your progress stays only in this browser.