Understanding the Implementation and Traversal of Modifier Chains in Jetpack Compose
This article explains how Jetpack Compose's Modifier interface is defined, how its concrete implementations such as SizeModifier, Background, Padding, CombinedModifier, and ComposedModifier form a chain, and how the foldIn and foldOut functions enable forward and reverse traversal of that chain for layout processing.
When developing UI with Jetpack Compose, the order of Modifier calls directly influences the final appearance of a component because each call adds an element to a Modifier chain that Compose processes sequentially.
The Modifier type is an interface that declares several utility functions ( foldIn , foldOut , any , all , and the infix then ) and a nested Element interface. The source shows the definition:
interface Modifier {
fun
foldIn(initial: R, operation: (R, Element) -> R): R
fun
foldOut(initial: R, operation: (Element, R) -> R): R
fun any(predicate: (Element) -> Boolean): Boolean
fun all(predicate: (Element) -> Boolean): Boolean
infix fun then(other: Modifier): Modifier = if (other === Modifier) this else CombinedModifier(this, other)
interface Element : Modifier { ... }
companion object : Modifier { ... }
}Three direct implementations exist: the companion object Modifier (used when calling Modifier.xxx() ), the internal sub‑interface Modifier.Element , and CombinedModifier , which links two modifiers together.
Calling a function such as Modifier.size(100.dp) creates a SizeModifier instance that implements LayoutModifier , a subclass of Modifier.Element . The source snippet illustrates this:
fun Modifier.size(size: Dp) = this.then(
SizeModifier(
...
)
)Similarly, Modifier.background(Color.Red) creates a Background (a DrawModifier ) which also implements Modifier.Element . Each call uses the then infix to attach the new element to the existing chain.
The then implementation in the companion object simply returns the new modifier, while CombinedModifier stores the previous ( outer ) and new ( inner ) modifiers as private fields:
class CombinedModifier(
private val outer: Modifier,
private val inner: Modifier
) : Modifier {
override fun
foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
// foldOut is analogous but traverses in reverse order
}Because outer and inner are private, external code cannot directly iterate the chain. Instead, Compose provides foldIn (forward) and foldOut (reverse) traversal functions. They accept an initial accumulator and a lambda that processes each Element . Example counting elements:
val modifier = Modifier
.size(100.dp)
.background(Color.Red)
.padding(10.dp)
.pointerInput(Unit) { /*...*/ }
val count = modifier.foldIn
(0) { current, element ->
Log.d("compose_study", "index: $current, element: $element")
current + 1
}The traversal works because CombinedModifier.foldIn first folds the outer part, then folds the inner part, ultimately reaching the concrete Element where the lambda is finally invoked.
Some modifiers, such as those created by Modifier.pointerInput , use the composed helper. composed wraps a factory lambda inside a ComposedModifier , which later gets flattened by the Compose runtime. The flattening occurs in Composer.materialize where each ComposedModifier is replaced by the modifier produced by its factory, and the process recurses until no ComposedModifier remains.
fun Composer.materialize(modifier: Modifier): Modifier {
val result = modifier.foldIn
(Modifier) { acc, element ->
acc.then(
if (element is ComposedModifier) {
@Suppress("UNCHECKED_CAST")
val factory = element.factory as Modifier.(Composer, Int) -> Modifier
val composedMod = factory(Modifier, this, 0)
materialize(composedMod)
} else element
)
}
return result
}After flattening, the final Modifier chain is passed to layout nodes via materializerOf(modifier) inside the Layout composable, where another traversal (using foldOut ) builds the internal LayoutNodeWrapper chain.
In summary, the article demystifies the underlying data structures of Jetpack Compose's Modifier system, showing how each call contributes to a linked chain, how CombinedModifier links nodes, and how foldIn / foldOut enable safe traversal despite private fields, ultimately helping developers debug and reason about UI behavior.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.