CompositionLocalConsumerModifierNode


Implementing this interface allows your Modifier.Node subclass to read CompositionLocals via the currentValueOf function. The values of each CompositionLocal will be resolved based on the context of the layout node that the modifier is attached to, meaning that the modifier will see the same values of each CompositionLocal as its corresponding layout node.

import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.InspectorInfo

val localBackgroundColor = compositionLocalOf { Color.White }
class BackgroundColor :
    Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode {
    override fun ContentDrawScope.draw() {
        val backgroundColor = currentValueOf(localBackgroundColor)
        drawRect(backgroundColor)
        drawContent()
    }
}
val backgroundColorElement =
    object : ModifierNodeElement<BackgroundColor>() {
        override fun create() = BackgroundColor()

        override fun update(node: BackgroundColor) {}

        override fun hashCode() = System.identityHashCode(this)

        override fun equals(other: Any?) = (other === this)

        override fun InspectorInfo.inspectableProperties() {
            name = "backgroundColor"
        }
    }
fun Modifier.backgroundColor() = this then backgroundColorElement
Box(Modifier.backgroundColor()) { Text("Hello, world!") }

Summary

Extension functions

T

Returns the current value of local at the position in the composition hierarchy of this modifier's attached layout node.

Cmn
inline Boolean

Evaluates a boolean query against the current UiMediaScope from a Modifier.Node.

Cmn

Inherited functions

From androidx.compose.ui.node.DelegatableNode
open Unit

Invoked when the density changes for this node.

Cmn
open Unit

Invoked when the layout direction changes for this node.

Cmn

Inherited properties

From androidx.compose.ui.node.DelegatableNode
Modifier.Node

A reference of the Modifier.Node that holds this node's position in the node hierarchy.

Cmn

Extension functions

currentValueOf

fun <T : Any?> CompositionLocalConsumerModifierNode.currentValueOf(
    local: CompositionLocal<T>
): T

Returns the current value of local at the position in the composition hierarchy of this modifier's attached layout node.

Unlike CompositionLocal.current, reads via this function are not automatically tracked by Compose. Modifiers are not able to recompose in the same way that a Composable can, and therefore can't receive updates arbitrarily for a CompositionLocal.

Because CompositionLocals may change arbitrarily, it is strongly recommended to ensure that the composition local is observed instead of being read once. If you call currentValueOf inside of a modifier callback like LayoutModifierNode.measure or DrawModifierNode.draw, then Compose will track the CompositionLocal read. This happens automatically, because these Compose UI phases take place in a snapshot observer that tracks which states are read. If the value of the CompositionLocal changes, and it was read inside of the measure or draw phase, then that phase will automatically be invalidated.

For all other reads of a CompositionLocal, this function will not notify you when the value of the local changes. Modifier.Node classes that also implement ObserverModifierNode may observe CompositionLocals arbitrarily by performing the lookup in an observeReads block. To continue observing values of the CompositionLocal, it must be read again in an observeReads block during or after the ObserverModifierNode.onObservedReadsChanged callback is invoked. See below for an example of how to implement this observation pattern.

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.node.observeReads

val LocalValue = compositionLocalOf { "abc123" }
class ValueObserverModifierNode :
    Modifier.Node(), CompositionLocalConsumerModifierNode, ObserverModifierNode {
    private var observedValue: String? = null

    override fun onAttach() {
        onObservedReadsChanged()
    }

    override fun onDetach() {
        observedValue = null
    }

    override fun onObservedReadsChanged() {
        observeReads {
            observedValue = currentValueOf(LocalValue)
            // Do something with the new value
        }
    }
}

This function will fail with an IllegalStateException if you attempt to read a CompositionLocal before the node is attached or after the node is detached.

Parameters
local: CompositionLocal<T>

The CompositionLocal to get the current value of

Returns
T

The value provided by the nearest CompositionLocalProvider component that invokes, directly or indirectly, the composable function that this modifier is attached to. If local was never provided, its default value will be returned instead.

mediaQuery

@ExperimentalMediaQueryApi
inline fun CompositionLocalConsumerModifierNode.mediaQuery(query: UiMediaScope.() -> Boolean): Boolean

Evaluates a boolean query against the current UiMediaScope from a Modifier.Node.

This function is designed to be used within a Modifier.Node that implements CompositionLocalConsumerModifierNode.

If called within a snapshot-aware context like LayoutModifierNode.measure or DrawModifierNode.draw callbacks, the reads within the query will be tracked, and the scope will be invalidated when the properties change.

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.mediaQuery
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset

// Example of a custom padding modifier that uses [mediaQuery] within a [Modifier.Node].
class AdaptivePaddingNode :
    Modifier.Node(), LayoutModifierNode, CompositionLocalConsumerModifierNode {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints,
    ): MeasureResult {
        val isLargeScreen = mediaQuery { windowWidth > 600.dp && windowHeight > 400.dp }

        // Adjust padding or size based on the query result
        val extraPadding = if (isLargeScreen) 80.dp.roundToPx() else 16.dp.roundToPx()
        val totalPaddingOnAxis = 2 * extraPadding

        // Measure the content with added padding
        val placeable =
            measurable.measure(constraints.offset(-totalPaddingOnAxis, -totalPaddingOnAxis))

        val width = constraints.constrainWidth(placeable.width + totalPaddingOnAxis)
        val height = constraints.constrainHeight(placeable.height + totalPaddingOnAxis)

        return layout(width, height) { placeable.place(extraPadding, extraPadding) }
    }
}

class AdaptivePaddingElement : ModifierNodeElement<AdaptivePaddingNode>() {
    override fun create() = AdaptivePaddingNode()

    override fun update(node: AdaptivePaddingNode) {}

    override fun equals(other: Any?) = other === this

    override fun hashCode() = 0
}

@Stable fun Modifier.adaptivePadding(): Modifier = this.then(AdaptivePaddingElement())

Box(Modifier.adaptivePadding().background(Color.Blue).size(400.dp))
Parameters
query: UiMediaScope.() -> Boolean

A lambda expression with UiMediaScope as its receiver, representing the condition to check.

Returns
Boolean

The immediate boolean result of the query.