Best Practices in Jetpack Compose for beginners

Improve your app performance

Bhoomi Vaghasiya Gadhiya
4 min readSep 22, 2023

Jetpack Compose, Android’s recommended, modern toolkit for building native UI — can simplify and accelerate UI development. However, it does present a learning curve, especially those new to declarative UI frameworks.

In this article, I will discuss best practices to follow when building an application using Jetpack Compose. These practices are especially useful for beginners who may find it challenging to improve their app’s performance and code readability.

Conditional Modifier

When building an application, be it small or large, there will often be a need to apply conditions to modifiers. For instance, you may want to change the background color of a button based on whether the input is valid or not. In such cases, many developers do something like this:

modifier = Modifier.background(if(isValidInput) Color.Black else Color.Gray)  

However, this approach may not be the most efficient way to handle conditional modifiers, especially when the condition is based on a dynamic property that frequently changes. In Compose, recomposition can occur frequently, and recomposing the entire Composable tree due to a small change like toggling a background color can lead to performance issues.

While this one-liner might seem concise, it can become less readable as you add more conditions or complex logic. To address this, consider using the Modifier.then() function to chain modifiers together.

// Best practice
modifier = Modifier.then(
if (isValidInput) {
Modifier.background(Color.Black)
} else {
Modifier.background(Color.Gray)
}
)

Using keys in Lazy Columns or Lazy Rows

When working with Lazy Columns or Lazy Rows, make sure to use the key for each item in the lazy column. It can help Compose to track the identity of each item and efficiently recompose the column when necessary. This will also prevent unwanted recompositions especially when you are changing the position or order of the items in the list.

For example:

@Composable
fun MyComposable(countries:List<Country>) {
LazyColumn{
items(countries.size, key = {it.id}){
//content
}
}
}

Reusable Composable

One of the most valuable features of Jetpack Compose is Reusable composable. Therefore, It is important to follow best practices when working with them.

For example:

@Composable
fun MyCustomButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier
.fillMaxWidth() //remove this
.clip(RoundedCornerShape(10.dp))
.background(Color.Blue)
.clickable {
onClick
}) {
Text(text = text)
}
}

In this Composable, it’s not advisable to use fillMaxWidth() directly. If you use fillMaxWidth() or fillMaxHeight() within the Composable, it can lead to unexpected layout results when you use it in various contexts.

For instance, if you place two MyCustomButton Composables in a row, may not behave as expected because both buttons will try to cover the full width of the parent composable.

Therefore, it’s better to remove fillMaxWidth() from the Composable itself and add it to the modifier parameter whenever you call MyCustomButton

Keep expensive operations to a minimum with remember {} and derivedStateOf {}

Jetpack Compose recomposes composable functions when their inputs change. This can be a performance problem if you have expensive calculations in your composable functions. To avoid this, you can use the remember {} and derivedStateOf {} functions.

The remember {} function caches the value of the expression inside it. This means that the expression will only be calculated once, even if the composable function is recomposed multiple times.

Here is an example of how to use remember {}:

val data = remember {
// This calculation will only be performed once
readDataFromDisk()
}

While remember {} is great for one-time calculations, it might not be the best choice when you have a stream of values changing frequently. In such cases, you can use derivedStateOf {}.Here is an example of how to use derivedStateOf {}:

val state by viewModel.state.collectAsState()

val sorted by remember(state.listData) {
derivedStateOf { state.listData.sorted() }
}

In this example, state.listData can change frequently. Instead of recalculating sorted with every change, derivedStateOf {} ensures that sorting only happens when state.listData changes.

If you want to learn more about derivedStateOf , check out my article here.

The best practices outlined in this article are not compulsory to follow, but they are highly recommended. By following these practices, you can minimize the computational cost and ensure that your Composables stay efficient, even as they get called and recomposed during the app’s lifecycle.

If you are new to Jetpack Compose, I encourage you to start by following these best practices. As you become more familiar with the framework, you can experiment with different approaches and find what works best for you and your projects.

I hope this article was helpful. Thank you for reading!

Don’t forget to clap only if you think I deserve it👏

If you have any queries related to Android, I’m always happy to help you. You can reach me on LinkedIn and Twitter.

Happy Learning🚀 Happy Composing 📱

--

--

Bhoomi Vaghasiya Gadhiya

Android Developer 📱 | Enthusiastic about helping others 🤝 | | Let's code to create a better world! 🌟