Google is committed to advancing racial equity for Black communities. See how.

Applier

interface Applier<N>
androidx.compose.runtime.Applier

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 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.compositionFor
import androidx.compose.runtime.emit
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 insert(index: Int, instance: Node) {
        current.children.add(index, instance)
    }

    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(content: @Composable () -> Unit): Composition {
    return compositionFor(this, NodeApplier(this), Recomposer.current()).also {
        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 = {}) {
    emit<TextNode, NodeApplier>(::TextNode) {
        set(text) { this.text = it }
        set(onClick) { this.onClick = it }
    }
}

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

// and then a sample tree could be composed:
fun runApp(root: GroupNode) {
    root.setContent {
        var count by remember { mutableStateOf(0) }
        Group {
            Text("Count: $count")
            Text("Increment") { count++ }
        }
    }
}

Summary

Public methods
abstract Unit

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 Unit
down(node: N)

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

abstract Unit
insert(index: Int, instance: N)

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

abstract Unit
move(from: Int, to: Int, count: Int)

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

abstract Unit
remove(index: Int, count: Int)

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

abstract Unit
up()

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

Properties
abstract N

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

Public methods

clear

abstract fun clear(): Unit

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 fun down(node: N): Unit

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.

insert

abstract fun insert(
    index: Int,
    instance: N
): Unit

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

move

abstract fun move(
    from: Int,
    to: Int,
    count: Int
): Unit

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

The to index is related 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.

remove

abstract fun remove(
    index: Int,
    count: Int
): Unit

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

up

abstract fun up(): Unit

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.

Properties

current

abstract val current: N

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.