How are people conditionally applying their Modifi...
# compose
d
How are people conditionally applying their Modifiers? Would a utility like this make sense (and be correct?):
Copy code
fun Modifier.conditional(
    condition: Boolean,
    positive: Modifier.() -> Modifier,
    negative: Modifier.() -> Modifier = { this }
): Modifier = if (condition) positive(this) else negative(this)

fun <T> Modifier.nullConditional(
    value: T?,
    positive: Modifier.(T) -> Modifier,
    negative: Modifier.() -> Modifier = { this }
): Modifier = value?.let {
    positive(this, value)
} ?: negative(this)
s
Use
then
?
Copy code
Modifier
    .size(500.dp)
    .then(
        if (condition) {
            Modifier.background(Color.Green)
        } else {
            Modifier.background(Color.Red)
        }
    )
Or even extract it, if it's used often:
Copy code
fun Modifier.redOrGreen(condition: Boolean): Modifier {
    return if (condition) {
        this.background(Color.Green)
    } else {
        this.background(Color.Red)
    }
}
Copy code
Modifier
    .size(500.dp)
    .then(Modifier.redOrGreen(condition)
d
is that any better than what I have really? Not sure if I am paying any penalty with the functional type
m
The
conditional
function could be made
inline
to avoid any penalty. But in general a nice named function give more context then generic functions like
conditional
. Not sure if the
then
approach is more readable at the call site. My guess is that they are both very similar.
s
Yeah, either is fine, but I would personally lean more towards the
then
approach, myself. The penalty is negligible in either case, IMO. 👍
d
We have something like this for readability:
Copy code
@Composable
inline fun Modifier.applyIf(condition: Boolean, block: @Composable Modifier.() -> Modifier): Modifier =
    if (condition) {
        this.then(Modifier.block())
    } else {
        this
    }
But in general I'd avoid stuff like this unless they are very clear and actually add value for reading the code. E.g if you provide two lambdas and don't use named arguments it would be non-trivial to know what it would do. In the case you showed above I'd use
Modifier.then()
with and if statement
d
We use named arguments so I am not too worried about the readability in this case. I'll add
inline
for now and review all our call-sites to make sure it's actually an improvement over pure
then
.
d
I'd argue that the need for a named argument for such a simple thing, hints of it being code smell. 😬
d
Yeah possibly, it's very rare that we use these hence I was wondering if we should straight-up just delete them. Probably more a team preference than a definite code smell tho, the
.conditional
example and
.applyIf
functions are not much different for example.
a
I have this in my codebase and it's work fine
Copy code
fun Modifier.thenIf(
    predicate: Boolean,
    ifTrue: Modifier.() -> Modifier,
): Modifier {
    return when {
        predicate -> then(ifTrue(Modifier))
        else -> this
    }
}

fun Modifier.thenIf(
    predicate: Boolean,
    ifTrue: Modifier.() -> Modifier,
    ifFalse: Modifier.() -> Modifier,
): Modifier {
    return then(
        when {
            predicate -> ifTrue(Modifier)
            else -> ifFalse(Modifier)
        }
    )
}

fun <T : Any> Modifier.thenIfNotNull(
    element: T?,
    ifTrue: Modifier.(T) -> Modifier,
): Modifier {
    return when {
        element != null -> then(ifTrue(element))
        else -> this
    }
}

fun <T> Modifier.thenIfNotNull(
    element: T?,
    ifTrue: Modifier.(T) -> Modifier,
    ifFalse: Modifier.() -> Modifier,
): Modifier {
    return then(
        when {
            element != null -> ifTrue(element)
            else -> ifFalse(Modifier)
        }
    )
}
1