Compose, Crafting with Conditional Modifiers

Tezov
ITNEXT
Published in
5 min readMay 8, 2024

--

This short article will discuss how you can conditionally add modifiers using sugar syntax. To achieve this, custom extensions are the only way. Initially, I’ll demonstrate what can be done without our extensions, followed by an exploration of how having these extensions can simplify your life.

Pixabay : Satheesh Sankaran

Table of Contents

  1. Without extensions
  2. With extensions
  3. Extensions source code
  4. Bonus
  5. Final thoughts

1. Without extensions

I will use the alpha modifier as an example. This approach can be applied to all modifiers, although some may not easily accommodate the if-else construct.

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.alpha(
if (my_condition) my_true_value
else my_false_value
)
)

So, we need to utilize an if-else construct. While it works well, what if we occasionally need to include the alpha modifier and other times not? There are two approaches:

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.alpha(
if (my_condition) my_true_value
else 1.0f // we have to add the alpha modifier at 1.0f even if we don't need it
)
)

What we really aim to accomplish is:

val modifier = Modifier
.size(60.dp)
.background(Color.Blue)

if (my_condition) modifier.alpha(my_true_value)

Box(modifier = modifier)

With this approach, we only add the alpha modifier when necessary. However, I’m not fond of this method, as it involves creating a variable above the Box.

As mentioned earlier, I used the alpha modifier in the example because it easily accommodates the if-else construct. However, let’s strive for clarity when discussing other modifiers, for instance.

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue,
if (my_condition) CircleShape
else RectangleShape // You must specify the Rectangle shape explicitly.
)
)

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.padding(
if (my_condition) 12.dp
else 0.dp // You must specify 0.dp explicitly.
)
)

It compels us to utilize some default value where we could simply omit the modifier altogether. However, as mentioned earlier, the alternative method is not an acceptable solution.

2. With extensions

Let’s revisit the same scenarios mentioned above but in a manner that aligns with our preferred writing style.

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnTrue(my_condition) {
alpha(my_true_value) // Here alpha is added only when needed
}
)

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnTrue(my_condition) {
clip(CircleShape) // Here the shape is applied through clip only when needed
}
)

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnTrue(my_condition) {
padding(12.dp) // Here padding is added only when needed
}
)

I greatly prefer this style of writing. We could also apply it to false conditions or even include multiple modifiers within the true condition block.


// Our extension must manage also false condition alone.
Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnFalse(my_condition) {
alpha(my_false_value) // Here alpha is added only when needed
.padding(start = 12.dp) // Yes we can chain modifier inside the block condition!
}
)

// Our extension must be able to manage true and false condition in one shot
Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.then(
my_condition,
onTrue = {
padding(vertical = 12.dp)
clip(CircleShape)
},
onFalse = {
alpha(0.5)
.shadow(2.dp)
}
)
)

// What about null ? I did it too, I'll show you the source code of
// theses extensions in the next section

3. Extensions source code

So, the initial part consists of our three function signatures: “thenOnTrue”, “thenOnFalse”, and “then”.

@Composable
fun Modifier.thenOnTrue(
condition: Boolean,
block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onTrue = block)

@Composable
fun Modifier.thenOnFalse(
condition: Boolean,
block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onFalse = block)

@Composable
fun Modifier.then(
condition: Boolean,
onTrue: @Composable Modifier.() -> Modifier,
onFalse: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onTrue, onFalse)

Since everything was factorizable, I created only one function extension called “thenInternal” and utilized it in the three signature calls. Here is the code for “thenInternal”:

@Composable
private fun Modifier.thenInternal(
condition: Boolean,
onTrue: (@Composable Modifier.() -> Modifier)? = null,
onFalse: (@Composable Modifier.() -> Modifier)? = null
) = (if (condition) {
onTrue?.let { then(Modifier.it()) }
} else {
onFalse?.let { then(Modifier.it()) }
}) ?: this

That’s all there is to it. You can use it with any modifier and chain multiple conditional modifiers to achieve whatever you desire!

All modifiers have been scoped with “@Composable” because I want to be able to use composed scoped variable inside the onFalse or onTrue block.

Box(
modifier = Modifier
.background(Color.Blue)
.thenOnTrue(my_condition) {
size(MyLocalTheme.smallBox)
// You can also use composable scoped variables
// like LocalContext.current
// or even "val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)"
}
)

Early Compose day

Before, it was not recommended to use “@Composable” on modifier extension. It was recommended to use composed block instead. So the first version was looking like this :

// *** Old way *** Not recommended anymore

// Composable annotation here was not recommended
private fun Modifier.thenInternal(
condition: Boolean,
onTrue: (@Composable Modifier.() -> Modifier)? = null,
onFalse: (@Composable Modifier.() -> Modifier)? = null
) = (if (condition) {
onTrue?.let { composed { then(Modifier.it()) } } // so we used composed block
} else {
onFalse?.let { composed { then(Modifier.it()) } }
}) ?: this

4.Bonus

As a bonus, we can apply the exact same approach to handle null condition values as well.

@Composable
fun <T : Any> Modifier.thenOnNotNull(
condition: T?,
block: @Composable Modifier.(T) -> Modifier
) = thenInternal(condition, onNotNull = block)

@Composable
fun <T : Any> Modifier.thenOnNull(
condition: T?,
block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onNull = block)

@Composable
fun <T : Any> Modifier.then(
condition: T?,
onNotNull: @Composable Modifier.(T) -> Modifier,
onNull: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onNotNull, onNull)

private fun <T : Any> Modifier.thenInternal(
condition: T?,
onNotNull: (@Composable Modifier.(T) -> Modifier)? = null,
onNull: (@Composable Modifier.() -> Modifier)? = null
) = (condition?.let {
onNotNull?.let { then(Modifier.it(condition))}
} ?: run {
onNull?.let { then(Modifier.it()) }
}) ?: this

To utilize them:

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnNull(my_object_which_can_be_null) {
alpha(0.8f) // Here alpha is added only when needed
.otherModifier()
}
)

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.thenOnNotNull(my_object_which_can_be_null) {
padding(12.dp) // Here padding is added only when needed
.otherModifier()
}
)

Box(
modifier = Modifier
.size(60.dp)
.background(Color.Blue)
.then(
my_object_which_can_be_null,
onNotNull = {
padding(vertical = 12.dp)
clip(CircleShape)
},
onNull = {
alpha(0.5)
.shadow(2.dp)
}
)
)

5. Final thoughts

This time, I won’t provide a source code repository. I believe it’s unnecessary since all the code is presented here and consists solely of extensions.

I firmly believe that practical extensions like these can significantly simplify your life and enhance readability. Feel free to experiment with them and even share your experiences!

If you notice the structure of this article, I first determined the type of friendly writing I desired and then crafted it as an extension. This approach should be applicable in almost all situations when designing something — first envision what you want, then execute it! Of course, unexpected discoveries may occur along the way, especially when trying something new. ^^

Don’t hesitate to comment or request more information. If you find this story helpful, consider following or subscribing to the newsletter. Thanks.

I’ll gladly also accept any support to help me to write more content.

--

--

Born with a C real-time system background. Nowadays, i'm a fanatic Android developer and Swift lover.