Blog Infos
Author
Published
Topics
Published
Streamline your UI code

When we get down to it, software is just a whole lot of if statements…

 

Modifiers in Jetpack Compose are super powerful, and sometimes we only want to add a modifier if a certain condition is met, but this can quickly get out of hand when the conditions get complex. It can also become quite unreadable and hard for other developers to trace what is being modified and why.

Setting the scene

Take a look at this example, I have a simple scene that switches between dark mode and light mode and a torch turns on and off:

This scene is quite complex to construct with a different Modifier set for each condition:

@Composable
fun ComplexModifierScene(
nighttime: Boolean,
torchon: Boolean,
modifier: Modifier = Modifier
) {
val boxModifier = if (nighttime && torchon) {
modifier
.clip(CircleShape)
.fillMaxHeight()
.aspectRatio(1f)
.background(TorchLight)
.shadow(elevation = 4.dp, shape = CircleShape)
} else if (!nighttime) {
modifier
.fillMaxHeight()
.aspectRatio(1f)
.background(Sunlight)
} else {
modifier
.fillMaxHeight()
.aspectRatio(1f)
.background(Night)
.blur(20.dp)
}
Box(boxModifier) {
Image(painterResource(R.drawable.outline_yard_24), "Scene", Modifier.fillMaxSize())
}
}

I can try and simplify these modifiers to reduce duplicated code:

@Composable
fun ComplexModifierScene(
nighttime: Boolean,
torchon: Boolean,
modifier: Modifier = Modifier
) {
val backgroundColor = if (nighttime && torchon) {
TorchLight
} else if (!nighttime) {
Sunlight
} else {
Night
}
val boxModifier = modifier.background(backgroundColor)
.fillMaxHeight()
.aspectRatio(1f)
val updatedModifier = if (nighttime && torchon) {
boxModifier
.clip(CircleShape)
.shadow(elevation = 4.dp, shape = CircleShape)
} else if (!nighttime) {
boxModifier
} else {
boxModifier
.blur(20.dp)
}
Box(updatedModifier) {
Image(painterResource(R.drawable.outline_yard_24), "Scene", Modifier.fillMaxSize())
}
}

But this is hard to read and understand what is applied when. And for some cases (nighttime == false) we are just passing through the boxModifier as is to satisfy the if statement return value. Also, in order to simplify I have had to change the order of some of the modifiers which has resulted in the clip & background modifier not being applied in the correct order, meaning the background goes outside the clip area:

The original scene on the left, the ‘simplified’ but wrong scene on the right

 

Create a conditional modifier

What we can do, is construct an extension function that will check the condition and then apply the modifier depending on the result of the condition:

fun Modifier.conditional(
condition: Boolean,
ifTrue: Modifier.() -> Modifier,
ifFalse: (Modifier.() -> Modifier)? = null,
): Modifier {
return if (condition) {
then(ifTrue(Modifier))
} else if (ifFalse != null) {
then(ifFalse(Modifier))
} else {
this
}
}
view raw ModifierUtil.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

Making use of the Modifier concatenation function then and lambdas in the function signature we can easily chain modifiers one after another.

This can be improved for performance by using the inline Kotlin modifier (thank-you for pointing this out Ricardo Carrapiço!):

inline fun Modifier.conditional(
condition: Boolean,
ifTrue: Modifier.() -> Modifier,
ifFalse: Modifier.() -> Modifier = { this },
): Modifier = if (condition) {
then(ifTrue(Modifier))
} else {
then(ifFalse(Modifier))
}
view raw ModifierUtil.kt hosted with ❤ by GitHub

Adding this into the scene:

@Composable
fun ComplexModifierScene(
nighttime: Boolean,
torchon: Boolean,
modifier: Modifier = Modifier
) {
val backgroundColor = if (nighttime && torchon) {
TorchLight
} else if (!nighttime) {
Sunlight
} else {
Night
}
val boxModifier = modifier
.conditional(nighttime && torchon, {
clip(CircleShape)
})
.fillMaxHeight()
.aspectRatio(1f)
.background(backgroundColor)
.conditional(nighttime && torchon, {
shadow(elevation = 4.dp, shape = CircleShape)
})
.conditional(nighttime && !torchon, {
blur(20.dp)
})
Box(boxModifier) {
Image(painterResource(R.drawable.outline_yard_24), "Scene", Modifier.fillMaxSize())
}
}

This is now much easier to read. To even further simplify the conditions, we can nest them.

@Composable
fun ComplexModifierScene(
nighttime: Boolean,
torchon: Boolean,
modifier: Modifier = Modifier
) {
val backgroundColor = if (nighttime && torchon) {
TorchLight
} else if (!nighttime) {
Sunlight
} else {
Night
}
val boxModifier = modifier
.conditional(nighttime && torchon, {
clip(CircleShape)
})
.fillMaxHeight()
.aspectRatio(1f)
.background(backgroundColor)
.conditional(nighttime, {
conditional(
condition = torchon,
ifTrue = {
shadow(elevation = 4.dp, shape = CircleShape)
},
ifFalse = {
blur(20.dp)
}
)
})
Box(boxModifier) {
Image(painterResource(R.drawable.outline_yard_24), "Scene", Modifier.fillMaxSize())
}
}

This gives the exactly the same scene as the original scene as we can respect the modifier ordering.

Exactly the same as the original

 

Null conditional modifier

A common condition that you may want to check is if something is null or not, we can also create a nullConditional modifier that takes in a type argument and passes that in to the ifNotNull branch so it can be used by the modifier:

inline fun <T> Modifier.nullConditional(
argument: T?,
ifNotNull: Modifier.(T) -> Modifier,
ifNull: Modifier.() -> Modifier = { this },
): Modifier {
return if (argument != null) {
then(ifNotNull(Modifier, argument))
} else {
then(ifNull(Modifier))
}
}
view raw ModifierUtil.kt hosted with ❤ by GitHub

This can be used just the same as the regular conditional modifier, the condition variable value is passed into the ifNotNull lambda so it can be used in the modifier being applied. In this example, I am passing in a nullable colour that can be applied if present:

@Composable
fun ComplexModifierScene(
nighttime: Boolean,
torchon: Boolean,
backgroundColor: Color?,
modifier: Modifier = Modifier
) {
val boxModifier = modifier
.conditional(nighttime && torchon, ifTrue = {
clip(CircleShape)
})
.fillMaxHeight()
.aspectRatio(1f)
.nullConditional(backgroundColor, {
background(it)
})
.conditional(nighttime, {
conditional(
condition = torchon,
ifTrue = {
shadow(elevation = 4.dp, shape = CircleShape)
},
ifFalse = {
blur(20.dp)
}
)
})
Box(boxModifier) {
Image(painterResource(R.drawable.outline_yard_24), "Scene", Modifier.fillMaxSize())
}
}

If I pass in a grey colour and compare to the original:

The original code in this blog post was developed with help from John Ernest Ramos.

Take a look at the full code in this demo on Github:

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu