Tools to Detect and Debug Recomposition in Jetpack Compose


This post is extracted from my talk “What does recomposition mean to your app” .

What is recomposition

Recomposition is the action of forming something again or differently. When the term is used in Jetpack Compose, it means the action of executing the function again.

Jetpack Compose is a declarative UI toolkit in Android. The traditional XML-based style was imperative. The declarative style of building UI is focused on what to do, rather than how to do. It focuses on evaluating results based on input, rather than defining variables and changing their values.

Because Compose evaluates results based on the input of a function, a new input would trigger the recomposition that re-executes the function again.

Why do you need to be concerned about recomposition?

When building a feature in Jetpack Compose, you might notice a drop in your UI performance: some sort of jitteriness, lag, unexpected behavior, etc.

This happens because of improper implementation of composable functions. On one hand, it’s forcing your UI to be recomposed excessively and do more work than necessary, thus directly affecting the performance of your app. On the other hand, it doesn’t update your UI when you expect it to recompose, thus leading to unexpected behavior and bugs in your app.

When Compose updates a frame, it goes through three phases: Composition, Layout and Drawing. During these phases, Compose runs composable functions, builds the UI tree, determines size and placement of each element in the tree, and then renders each UI element.

It also has a built-in algorithm, smart recomposition, that can intelligently skip some of these tasks if they aren’t needed. However, improper implementation of composable functions makes it harder for Compose to know which tasks to skip, thus the recomposition can trigger these tasks all over again. It is potentially expensive in terms of time, computing power, and battery usage of a device.

In both cases, having a good set of tools, that will help you find issues, understand recomposition better, and learn the best practices of implementing composable functions, is beneficial for you to move through the learning curve of Jetpack Compose framework.

Tools to detect recomposition

I’ve compiled a list of tools that will help you to detect and debug recomposition in your app.

  1. Compose compiler metrics

Compiler metrics are reports about compose-specific concepts generated by Compose Compiler to understand what is happening with the code on fined-grain level.

ProsCons
1. Full-analysis including info about not skippable and restartable functions1. Should be completed on release build, which might not be ideal when building a feature

To lean more and enable the metrics, check out the post from Chris Banes on compiler metrics .

  1. Recompose Highlighter modifier from Google Play team
ProsCons
1. visually appealing1. in nested functions, it’s difficult to separate and know what function is recomposed
2. labor consuming - have to add to each modifier function
  1. Log statements

You might have not expected to see log statements in the list, however it is ancient way of debugging and even it can help you to debug recomposition, although it can get noisy and be difficult to filter, especially if you have a lot of functions. If your goal of logging is not to track recomposition, then use DisposableEffect or LaunchEffect.

Let’s overview some of the ways of placing log statements.

  • Adding directly in function
1@Composable
2fun Example() {
3    println("Example fun")
4    Column {
5    }
6}
  • Putting behind SideEffect
1@Composable
2fun Example() {
3    SideEffect {
4        println("Example fun")
5    }
6    Column {
7    }
8}
1@Composable
2fun Example() {
3    Column(
4        modifier = Modifier.recompositionCounter("column in Example")
5    ) {
6    }
7}
StrategyProsCons
1. Adding directly in functioneasy to addlogging happens even if recomposition failed or canceled → in this case it’s better to leverage SideEffect
2. Putting behind SideEffectruns after successful compositionwon’t trigger recomposition if you’re performing snapshot state reads in the log statement
3. Making a separate modifierruns after successful compositionlabor consuming - have to add to each modifier function
  1. Android Studio Electric Eel with recomposition counter

In Android Studio Electric Eel you can use the layout inspector to check how often a composable is recomposed or skipped .

ProsCons
1. shows number of compositions & number of skips1. it’s in canary build, so you have to accept the potential issues with the IDE
2. no need to manually add anything to the code2. you have to be in minimum compose version of 1.2.0-alpha03 in order to use the feature

Final Thoughts

In this article we learned about the importance of recomposition and its effect on the performance of the app. Currently there are four of ways of detecting and debugging recomposition:

  1. Compose compiler metrics
  2. Recompose highlighter modifier
  3. Log statements
  4. Android Studio Electric Eel

Let me know in comments if there are other ways to debug recomposition. I would love to learn more!


❤️ Was this post helpful?

If you liked the article, give a shoutout to @aida_isay on Instagram, Threads, or Twitter and help share this article.

Thank you for your support 🙌