A little bit of context
In the last couple of months, we’ve been busy playing around with Jetpack Compose. We had some challenges here and there in the migration, and it’s still a work in progress 🚧.
But this post is more about the fun part of Compose: MODIFIERS!!!! (and more)
What hell on earth are Modifiers you might ask 👀? As always, better to quote directly from Google:
Modifiers allow you to decorate or augment a composable. Modifiers let you do these sorts of things:
- 1. Change the composable’s size, layout, behavior, and appearance
2. Add information, like accessibility labels
3. Process user input
4. Add high-level interactions, like making an element clickable, scrollable, draggable, or zoomable
– from Google Documentation
ℹ️ In other words: Modifiers are the main way in Jetpack Compose to handle styling options, adjust the layout, handle events, and much more.
We’ll not look into how modifiers work per se, for that I’m once again redirecting you to my best friend Google’s Documentation 📚
We will instead have a look at my favorite part about Modifier, and Jetpack Compose in general: How easy it’s to reuse and extend the API. Which, you know, it’s probably one of the core fundaments of what we do as Software Engineerings.
Conditional Modifiers
Now, you know what else is a fundament of coding? Conditions.
For an Android Developer the most common way this is reflected in UI work it’s usually stuff like:
- If the price of a product is discounted, display it in a red box
- Use additional padding for the first item of a list
- Add a border if the button is enabled
You get the idea. We have a lot of cases where styling or layout attributes are applied based on a condition.
Let’s take this use case as an example:
We have a simple text, with 8dp padding horizontal and 4dp padding vertical.
If the price is below 15.00, display a squared (8dp) orange box
If the price is below 10.00, display a rounded (20dp) red box

Let’s take it easy and just render the base case:
@Composable
fun PriceText(price: Double) {
Text(
text = "$$price",
modifier = Modifier.padding(
top = 4.dp,
bottom = 4.dp,
start = 8.dp,
end = 8.dp,
),
)
}
Now to handle the different cases, you would probably use a when or some sort of conditional structure:
@Composable
fun PriceText(price: Double) {
val borderModifier = when {
price in (10.00..14.99) ->
Modifier
.border(
width = 1.dp,
color = Color.Orange,
shape = RoundedCornerShape(8.dp),
)
price < 10.00 ->
Modifier
.border(
width = 1.dp,
color = Color.Red,
shape = RoundedCornerShape(20.dp),
)
else -> Modifier
}
Text(
text = "$$price",
modifier = Modifier
.then(borderModifier)
.padding(
top = 4.dp,
bottom = 4.dp,
start = 8.dp,
end = 8.dp,
),
)
}
☝️ We make use of .then()
to append our custom modifier based on the conditions.
One could argue that we could just use a single modifier and have the color and size of the radius as values computed in the when
, both options are equally correct.
BUT
The thing is, in big layout files this style of handling conditional modifiers is quite hard to follow because you have to go back and forward between different lines.
It’s basically the equivalent of going back and forward between the old .xml files and .kt files.
We were over that 😤
Conditional Modifier Extensions
Well there’s nobody stopping you from actually using conditional structures inside the modifier such as:
@Composable
fun PriceText(price: Double) {
Text(
text = "$$price",
modifier = Modifier
.then(when {
price in (10.00..14.99) ->
Modifier
.border(
width = 1.dp,
color = Color.Orange,
shape = RoundedCornerShape(8.dp),
)
price < 10.00 ->
Modifier
.border(
width = 1.dp,
color = Color.Red,
shape = RoundedCornerShape(20.dp),
)
else -> Modifier
})
.padding(
top = 4.dp,
bottom = 4.dp,
start = 8.dp,
end = 8.dp,
),
)
}
But in our case, when using multiple conditions, it gets quite ugly. What about creating an extension to handle each condition?
Let’s call it applyIf()
😎
@Composable
fun PriceText(price: Double) {
Text(
text = "$$price",
modifier = Modifier
.applyIf(price in (10.00..14.99)) {
border(
width = 1.dp,
color = Color.Orange,
shape = RoundedCornerShape(8.dp),
)
}
.applyIf(price < 10.00) {
border(
width = 1.dp,
color = Color.Red,
shape = RoundedCornerShape(20.dp),
)
}
.padding(
top = 4.dp,
bottom = 4.dp,
start = 8.dp,
end = 8.dp,
),
)
}
Much better, we avoid having a full when
syntax but also remove the redundant else
branch and improving the overall readability.
Here are our two applyIf
extensions:
fun Modifier.applyIf(condition: Boolean, modifier: Modifier.() -> Modifier): Modifier {
return if (condition) {
modifier()
} else {
this
}
}
fun Modifier.applyIf(condition: Boolean, modifier: Modifier): Modifier {
return if (condition) {
then(modifier)
} else {
this
}
}
They’re basically the same, but the first one allows you to use Kotlin’s trailing lambdas and type-safe builders.
More Extensions!
We like Kotlin so much, especially extensions that we can improve some other aspects of the code 👨💻
Dp to Rounded Shape Extension
Chances are that creating ad-hoc RoundedCornerShape()
from various dimensions become quite frequent in your code base too. For that we use Dp.toRoundedShape() extension
.
fun Dp.toRoundedShape() = RoundedCornerShape(this)
// Before
shape = RoundedCornerShape(8.dp)
// After
shape = 8.dp.toRoundedShape()
This way we can keep that fluent dot dot dot syntax we all like 🧘
ℹ️ If you notice that the shape is used in a lot of places, we definitely recommend extracting it as part of your Theme.
Padding extensions
Let’s address another small problem, the padding()
modifier in compose. If you need to specify padding for one size or either vertical/horizontal you need to use the same function, granted with optional parameters.
BUT what if we could have extensions for each side and direction? 📐
fun Modifier.paddingTop(value: Dp = 0.dp) = padding(top = value)
fun Modifier.paddingBottom(value: Dp = 0.dp) = padding(bottom = value)
fun Modifier.paddingStart(value: Dp = 0.dp) = padding(start = value)
fun Modifier.paddingEnd(value: Dp = 0.dp) = padding(end = value)
fun Modifier.paddingVertical(value: Dp = 0.dp) = padding(vertical = value)
fun Modifier.paddingHorizontal(value: Dp = 0.dp) = padding(horizontal = value)
// Before
Modifier.padding(top = 8.dp)
Modifier.padding(start = 8.dp, end = 8.dp)
// After
Modifier.paddingTop(8.dp)
Modifier.paddingHorizontal(8.dp)
Preview Parameters
Finally, let me show you another cool feature from Compose we’ve been using lately.
This one was popularized in our team by @Andrea Geraldi and it’s a game-changer when it comes to previews.
I’m talking about Preview Parameters!!! A nice quick way to generate multiple previews for your composables using a single @Preview
function.
Wrapping it all together
Ok, so let’s see our initial PriceText()
composable taking into account all the extensions and the preview parameters:
class PriceDataProvider : PreviewParameterProvider<Double> {
override val values: Sequence<Double>
get() = sequenceOf(
23.99,
10.99,
8.23,
)
}
@Composable
fun PriceText(price: Double) {
Text(
text = "$$price",
modifier = Modifier
.applyIf(price in (10.00..14.99)) {
border(
width = 1.dp,
color = Color.Orange,
shape = 8.dp.toRoundedShape(),
)
}
.applyIf(price < 10.00) {
border(
width = 1.dp,
color = Color.Red,
shape = 20.dp.toRoundedShape(),
)
}
.paddingVertical(4.dp)
.paddingHorizontal(8.dp),
)
}
@Preview(showBackground = true)
@Composable
fun PreviewPriceText(@PreviewParameter(PriceDataProvider::class) price: Double) {
Surface(modifier = Modifier.padding(8.dp)) {
PriceText(price = price)
}
}

Conclusions
It’s pretty clear by now that we love our extensions in any shape and form.
And it turns out they work quite nicely with Compose and Modifiers to provide reusable code and improve readability.
We actually decided to move some of them, especially the Compose ones to a new module in our Design System Project under designsystem-utilities module.
Which by the way, is public and available in Maven Central 👀
To make you understand how much we love extensions, we even have a String.empty
extension to avoid having ""
in the codebase.
val String.Companion.empty: String
get() = ""
And I quote from @Luca Diotallevi:

You know what? We are actually looking for more people to tackle similar challenges.
If you’re also looking for new opportunities, check out our current openings.
Author: @GhimpuLucianEduard