{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

Applier

public interface Applier<N extends Object>

Known direct subclasses
AbstractApplier

An abstract Applier implementation.

Known indirect subclasses

An Applier is responsible for applying the tree-based operations that get emitted during a composition. Every Composer has an Applier which it uses to emit a ComposeNode.

A custom Applier implementation will be needed in order to utilize Compose to build and maintain a tree of a novel type.

import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.ComposeNode
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

// Provided we have a tree with a node base type like the following
abstract class Node {
    val children = mutableListOf<Node>()
}

// We would implement an Applier class like the following, which would teach compose how to
// manage a tree of Nodes.
class NodeApplier(root: Node) : AbstractApplier<Node>(root) {
    override fun insertTopDown(index: Int, instance: Node) {
        current.children.add(index, instance)
    }

    override fun insertBottomUp(index: Int, instance: Node) {
        // Ignored as the tree is built top-down.
    }

    override fun remove(index: Int, count: Int) {
        current.children.remove(index, count)
    }

    override fun move(from: Int, to: Int, count: Int) {
        current.children.move(from, to, count)
    }

    override fun onClear() {
        root.children.clear()
    }
}

// A function like the following could be created to create a composition provided a root Node.
fun Node.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    return Composition(NodeApplier(this), parent).apply {
        setContent(content)
    }
}

// assuming we have Node sub-classes like "TextNode" and "GroupNode"
class TextNode : Node() {
    var text: String = ""
    var onClick: () -> Unit = {}
}
class GroupNode : Node()

// Composable equivalents could be created
@Composable fun Text(text: String, onClick: () -> Unit = {}) {
    ComposeNode<TextNode, NodeApplier>(::TextNode) {
        set(text) { this.text = it }
        set(onClick) { this.onClick = it }
    }
}

@Composable fun Group(content: @Composable () -> Unit) {
    ComposeNode<GroupNode, NodeApplier>(::GroupNode, {}, content)
}

// and then a sample tree could be composed:
fun runApp(root: GroupNode, parent: CompositionContext) {
    root.setContent(parent) {
        var count by remember { mutableStateOf(0) }
        Group {
            Text("Count: $count")
            Text("Increment") { count++ }
        }
    }
}
See also
AbstractApplier
Composition
Composer
ComposeNode

Summary

Public methods

abstract void

Move to the root and remove all nodes from the root, preparing both this Applier and its root to be used as the target of a new composition in the future.

abstract void
down(@NonNull N node)

Indicates that the applier is getting traversed "down" the tree.

abstract @NonNull N

The node that operations will be applied on at any given time.

abstract void
insertBottomUp(int index, @NonNull N instance)

Indicates that instance should be inserted as a child of current at index.

abstract void
insertTopDown(int index, @NonNull N instance)

Indicates that instance should be inserted as a child to current at index.

abstract void
move(int from, int to, int count)

Indicates that count children of current should be moved from index from to index to.

default void

Called when the Composer is about to begin applying changes using this applier.

default void

Called when the Composer is finished applying changes using this applier.

abstract void
remove(int index, int count)

Indicates that the children of current from index to index + count should be removed.

abstract void
up()

Indicates that the applier is getting traversed "up" the tree.

Public methods

clear

abstract void clear()

Move to the root and remove all nodes from the root, preparing both this Applier and its root to be used as the target of a new composition in the future.

down

abstract void down(@NonNull N node)

Indicates that the applier is getting traversed "down" the tree. When this gets called, node is expected to be a child of current, and after this operation, node is expected to be the new current.

getCurrent

abstract @NonNullgetCurrent()

The node that operations will be applied on at any given time. It is expected that the value of this property will change as down and up are called.

insertBottomUp

abstract void insertBottomUp(int index, @NonNull N instance)

Indicates that instance should be inserted as a child of current at index. An applier should insert the node into the tree either in insertTopDown or insertBottomUp, not both. See the description of insertTopDown to which describes when to implement insertTopDown and when to use insertBottomUp.

insertTopDown

abstract void insertTopDown(int index, @NonNull N instance)

Indicates that instance should be inserted as a child to current at index. An applier should insert the node into the tree either in insertTopDown or insertBottomUp, not both.

The insertTopDown method is called before the children of instance have been created and inserted into it. insertBottomUp is called after all children have been created and inserted.

Some trees are faster to build top-down, in which case the insertTopDown method should be used to insert the instance. Other trees are faster to build bottom-up in which case insertBottomUp should be used.

To give example of building a tree top-down vs. bottom-up consider the following tree,

      R
|
B
/ \
A C

where the node B is being inserted into the tree at R. Top-down building of the tree first inserts B into R, then inserts A into B followed by inserting C into B`. For example,

    1           2           3
R R R
| | |
B B B
/ / \
A A C

A bottom-up building of the tree starts with inserting A and C into B then inserts B tree into R.

    1           2           3
B B R
| / \ |
A A C B
/ \
A C

To see how building top-down vs. bottom-up can differ significantly in performance consider a tree where whenever a child is added to the tree all parent nodes, up to the root, are notified of the new child entering the tree. If the tree is built top-down,

  1. R is notified of B entering.

  2. B is notified of A entering, R is notified of A entering.

  3. B is notified of C entering, R is notified of C entering.

for a total of 5 notifications. The number of notifications grows exponentially with the number of inserts.

For bottom-up, the notifications are,

  1. B is notified A entering.

  2. B is notified C entering.

  3. R is notified B entering.

The notifications are linear to the number of nodes inserted.

If, on the other hand, all children are notified when the parent enters a tree, then the notifications are, for top-down,

  1. B is notified it is entering R.

  2. A is notified it is entering B.

  3. C is notified it is entering B.

which is linear to the number of nodes inserted.

For bottom-up, the notifications look like,

  1. A is notified it is entering B.

  2. C is notified it is entering B.

  3. B is notified it is entering R, A is notified it is entering R, C is notified it is entering R.

which exponential to the number of nodes inserted.

move

abstract void move(int from, int to, int count)

Indicates that count children of current should be moved from index from to index to.

The to index is relative to the position before the change, so, for example, to move an element at position 1 to after the element at position 2, from should be 1 and to should be 3. If the elements were A B C D E, calling move(1, 3, 1) would result in the elements being reordered to A C B D E.

onBeginChanges

default void onBeginChanges()

Called when the Composer is about to begin applying changes using this applier. onEndChanges will be called when changes are complete.

onEndChanges

default void onEndChanges()

Called when the Composer is finished applying changes using this applier. A call to onBeginChanges will always precede a call to onEndChanges.

remove

abstract void remove(int index, int count)

Indicates that the children of current from index to index + count should be removed.

up

abstract void up()

Indicates that the applier is getting traversed "up" the tree. After this operation completes, the current should return the "parent" of the current node at the beginning of this operation.