https://kotlinlang.org logo
#compose-android
Title
# compose-android
s

Shivam Verma

03/29/2024, 3:18 PM
Hey everyone 👋 Is there a way to also set
CompositionLocal
values from a custom modifier ? It seems a
CompositionLocalConsumerModifierNode
can only read
CompositionLocal
values.
h

Halil Ozercan

03/29/2024, 4:22 PM
No you cannot set CompositionLocals from modifiers. They have their own local system called ModifierLocals.
z

Zach Klippenstein (he/him) [MOD]

03/29/2024, 5:00 PM
This is intentional btw. Data flows unidirectionally from composition down to modifiers, and into layout and draw
1
If you’re looking at modifier locals you probably just want to use TraversableNode instead, it’s more flexible and kind of replacing modifier locals
2
s

Shivam Verma

03/30/2024, 7:40 AM
Thanks for the tips!
I am trying to create a modifier to disable components using Zack's suggestion. In my case, disabling should only set
alpha = 0.5
. If an ancestor was already disabled using this modifier, I'd like to prevent alpha from being applied to a descendant again. I would also like to know whether this modifier is applied inside of the composable, so that I can further add disabling behaviors to the composable. I updated the node as follows where I now provide a
ModifierLocal
in addition to applying the alpha. A Composable can then read this
ModifierLocal
and check if the modifier was applied. Is this a good approach? Are there any improvements I could make?
Copy code
private class DisabledNode : Modifier.Node(),
    ModifierLocalModifierNode,
    TraversableNode,
    LayoutModifierNode {

    private var alphaLayerBlock: GraphicsLayerScope.() -> Unit = { alpha = 0.5f }
    private var defaultLayerBlock: GraphicsLayerScope.() -> Unit = {}

    override val providedValues: ModifierLocalMap = modifierLocalMapOf(ModifierLocalDisabledState)

    override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
        val hasDisabledAncestor = findNearestAncestor(traverseKey) != null
        if (!hasDisabledAncestor) provide(ModifierLocalDisabledState, State.Disabled)

        val placeable = measurable.measure(constraints)
        return layout(placeable.width, placeable.height) {
            placeable.placeWithLayer(
                x = 0,
                y = 0,
                layerBlock = if (!hasDisabledAncestor) alphaLayerBlock else defaultLayerBlock
            )
        }
    }

    override val traverseKey: Any = DragAndDropTraversableKey

    companion object {
        private object DragAndDropTraversableKey
    }
}
Update Composable behavior if
disabled
modifier is applied to any ancestor.
Copy code
fun CustomButton() {
    var enabled by remember { mutableStateOf(true) }
    TextButton(
        enabled = enabled,
        onClick = {},
        modifier = Modifier.modifierLocalConsumer { enabled = ModifierLocalDisabledState.current == State.Enabled }) {
        Text(text = "Button")
    }
}
z

Zach Klippenstein (he/him) [MOD]

03/30/2024, 7:28 PM
Don’t use modifier locals and traversal node together. Traversable node provides a superset of the functionality of modifier locals.
Composables can’t technically read modifier locals (or traversable nodes), only other modifiers.
If you need your composables to be aware of the alpha flag, you should just use composition locals.
s

Shivam Verma

03/30/2024, 7:42 PM
An alternative I could come up with is this (with the limitation that if this modifier is applied twice, the alpha will be applied twice as well).
Copy code
fun Modifier.disabled() = this
    .then(Modifier.alpha(0.5f))
    .then(Modifier.modifierLocalProvider(ModifierLocalDisabled) { State.Disabled })

@Composable
fun CustomButton(
    text: String,
    modifier: Modifier = Modifier
) {
    var enabled by remember { mutableStateOf(true) }
    OutlinedButton(
        enabled = enabled,
        onClick = {},
        modifier = modifier.modifierLocalConsumer { enabled = ModifierLocalDisabled.current == State.Enabled }) {
        Text(text = text)
    }
}
z

Zach Klippenstein (he/him) [MOD]

04/01/2024, 1:48 PM
The problem with that approach is that enabled won’t be set until the second frame. If its value changes from the first frame it will look like it flickers