I have a `Modifier` as follows ```fun Modifier.sel...
# compose
l
I have a
Modifier
as follows
Copy code
fun Modifier.selectedBackground(isSelected: Boolean): Modifier = if (isSelected) {
    composed {
        val alpha = if (isSelected) 0.16f else 0.22f
        Modifier.background(MaterialTheme.colorScheme.secondary.copy(alpha = alpha))
    }
}
I want to migrate it to
Modifier.Node
like,
Copy code
private class SelectedBackgroundNode(
    var isSelected: Boolean
): CompositionLocalConsumerModifierNode, DrawModifierNode, Modifier.Node()  {

    override fun ContentDrawScope.draw() {
        val backgroundColor = currentValue(MaterialTheme.colorScheme).background
        drawRect(color = backgroundColor, alpha = if (isSelected) 0.16f else 0.22f)
        drawContent()
    }
}
However, since
MaterialTheme.colorScheme
is basically
LocalColorScheme.current
and
LocalColorScheme
is internal, I’m unable to access it. The only way is passing
backgroundColor
as a parameter to
SelectedBackgroundNode
. An alternative could be declaring a new CompositionLocal
LocalAppColorScheme provides MaterialTheme.colorScheme
and use it. Is there any better way of accessing
MaterialTheme.colorScheme
m
i would say that modifier is overuse of composed api. Can’t you solve the same problem with .drawBehind { drawRect(color) } ?
Copy code
@Composable
fun Modifier.selectedBackground(
    color: Color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.16f),
    colorDisabled: Color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.22f),
    enabled: Boolean
): Modifier = this.then(
    Modifier.drawBehind {
        drawRect(
            if(enabled) color else colorDisabled
        )
    }
)
in any event, you can use the original code. It’s just fine.
just add the else clause 😛
l
Don't use
@Composable fun <http://Modifier.xxx|Modifier.xxx>(): Modifier
pattern, use
Modifier.composed
at least. The former has potential problem that you can't realize quickly, and it doesn't save much typing.
In this case, just use
Modifier.composed
. There are scenarios where you'd have to use
composed
.
m
That is kinda bogus and false. Composed modifer has a slight over head. This problem is simple and can be easily solved with a custom modifier
l
It's NOT false. It's actually very dangerous with the usage of CompositionLocals. The following example shows the difference:
Copy code
@Composable
fun Modifier.bad(): Modifier {
    return background(LocalContentColor.current)
}

fun Modifier.good(): Modifier {
    return composed {
        background(LocalContentColor.current)
    }
}

@Composable
fun TestBad() {
    val reusedModifier = Modifier
        .bad()
        .size(20.dp)

    Box(modifier = reusedModifier)

    Button(
        onClick = {}
    ) {
        Box(modifier = reusedModifier)
    }
}

@Composable
 fun TestGood() {
     val reusedModifier = Modifier
         .good()
         .size(20.dp)
 
     Box(modifier = reusedModifier)
 
     Button(
         onClick = {}
     ) {
         Box(modifier = reusedModifier)
     }
 }
The bad one behaves differently because
Modifier.bad
reads state in place, so inside Button it's not the actual LocalContentColor, instead it's that from outside Button. The best practice is to use Modifier.Node. However since LocalColorScheme is internal, we need to use some technique in order to access:
Copy code
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

import androidx.compose.material3.LocalColorScheme

LocalColorScheme // You made it!
Well, if you'd prefer the simpler one, just use it and have that in mind. I'd like the modifier to be more robust and context-free so I can reuse.
m
The examples are not the same. In the first you lose the previous modifiers, in the second since it is a built in modifier, it is already chained
l
They're both functionally correct. The first one
return background
is
return this then BackgroundElement
under the hood. The second one will be
return this then Modifier.background
effectively which is then also
return this then BackgroundElement
.
m
Ya true. My bad. You can still inline the modifier. I dont get it why you saying the composition local isnt picking the value. Inline should do it
l
Well, you could try it and see what I mean. It's not an inline problem, but @Composable. :)
m
The composition local picks the value closer to the current scope. I dont get what you are saying, it should work. Ill try when i can thanks
Oh ok. You separate the declaration. Ya i get it
I still see no reason to do that. Composed in substantially more expensive. If you know that happens, just use in place. Otherwise your custom modifiers will all have composed
l
Well, if you'd prefer the simpler one, just use it and have that in mind. I'd like the modifier to be more robust and context-free so I can reuse.
m
Besides, the built in modifiers have that problem as well, so