The Dangerous Deprecation of Analog Money

Understanding physical money is paramount to understanding how currency will evolve into the future. Physical money has to be retired if for no other reason than the logistics of keeping pallets of…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Jetpack Compose Stability Explained

This is a big post! Here is the TL/DR.

Notice the keyword there — “might”. Compose will trigger recomposition when snapshot state changes, and skip any composables that haven’t changed. Importantly though a composable will only be skipped if Compose can be sure that none of the parameters of a composable have been updated. Otherwise, if Compose can’t be sure, it will always be recomposed when its parent composable is recomposed. If Compose didn’t do this, it would lead to very hard to diagnose bugs with recomposition not triggering. It is much better to be correct and slightly less performant than incorrect but slightly faster.

Let’s use an example of a Row that displays contact details:

First, let’s say that we define the Contact class as an immutable data class, so it cannot be changed without creating a new object:

When the toggle button is clicked, we change the selected state. This triggers Compose to evaluate if the code inside ContactRow should be recomposed. When it comes to the ContactDetails composable, Compose will skip recomposing it. This is because it can see that none of the parameters, in this case contact, have changed. ToggleButton, on the other hand, inputs have changed and so it is recomposed correctly.

What about if our Contact class was defined like so?

Now our Contact class is no longer immutable, its properties could be changed without Compose knowing. Compose will no longer skip the ContactDetails composable as this class is now considered “unstable” (more details on what this means below). As such, anytime selected is changed, ContactRow will also recompose.

Now we know the theory on what Compose is trying to determine, let’s have a look at how it actually happens in practice.

Functions could be skippable and/or restartable:

Types could be immutable or stable:

When Compose state changes, Compose looks for the nearest restartable function above all of the points in the tree where those state objects are read. Ideally this will be the direct ancestor to re-run the smallest possible code. It is here that recomposition restarts. When re-executing the code, any skippable functions will be skipped if their parameters have not changed. Let’s take a look again at our earlier example

The steps of recomposition.

You might be thinking at this point, “this is really complicated! Why do I need to know this?!” and the answer is, you shouldn’t have to most of the time. Our goal is to have the compiler optimize code that you write naturally to be efficient. Skipping composable functions is an important ingredient to make that happen, but it’s also something that needs to be 100% safe or else it would result in very hard to diagnose bugs. For this reason, the requirements for a function to be skipped are strong. We are working to improve the compiler’s inference of skippability but there will always be situations where it is impossible for the compiler to work out. Understanding how skipping functions works under the hood in this situation can aid you in improving your performance but it should be considered only in cases where you have a measured performance issue caused by stability. A composable not being skippable may have no effect at all if the composable is lightweight or itself just contains skippable composables.

So what do you do if you can see your composable not being skipped even though none of its parameters have changed? The easiest thing to do is to check its definition and see if any of its parameters are clearly mutable. Are you passing in a type with var properties or a val property but with a known unstable type? If you are then that composable will never be skipped!

But what do you do when you can’t spot anything obviously wrong?

For debugging the stability of your composables you can run the task as follows:

If you instead run the composeCompilerMetrics task you will get overall statistics of the number of composables in your project and other similar info. This isn’t covered in this post as it’s not as useful for debugging.

This SnackCollection composable is completely restartable, skippable and stable. This is generally what you want, when possible, although far from mandatory (further detail at the end of the post).

However, let’s take a look at another example.

The HighlightedSnacks composable is not skippable — anytime this is called during recomposition, it will also recompose, even if none of its parameters have changed.

This is being caused by the unstable parameter, snacks.

Now we can switch to the classes.txt file to check the stability of Snack.

For reference, this is how Snack is declared

Snack is unstable. It has mostly stable parameters but the tags set is considered unstable. But why is this? Set appears to be immutable, it is not a MutableSet.

Unfortunately Set (as well as List and other standard collection classes, more on that soon) are defined as interfaces in Kotlin, this means that the underlying implementation may still be mutable. For example, you could write

The variable is constant, its declared type is not mutable but its implementation is still mutable. The Compose compiler cannot be sure of the immutability of this class as it just sees the declared type and as such declares it as unstable. Let’s now look into how we can make this stable.

When faced with an unstable class that is causing you performance issues, it is a good idea to attempt to make it stable. The first thing to try is just to make the class completely immutable.

In other words, make all var properties val, and all of those properties immutable types.

If this is impossible, then you will have to use Compose state for any mutable properties.

This means in practice, any mutable property should be backed by Compose state e.g mutableStateOf(…).

Back to the Snack example, the class appears immutable so how do we fix it?

There are multiple options you could take.

Switching the tags declaration to the following makes the Snack class stable.

Classes can also be annotated with either @Stable or @Immutable based on the rules above.

Annotating the Snack example would be done as follows:

Whichever method chosen, the Snack class will be inferred as Stable.

However, returning to the HighlightedSnacks composable, HighlightedSnacks is still not marked as skippable:

Parameters face the same problem as classes when it comes to collection types, List is always determined to be unstable, even when it is a collection of stable types.

You cannot mark an individual parameter as stable either, nor can you annotate a composable to always be skippable. So what can you do? Again there are multiple paths forwards.

Use a kotlinx immutable collection, instead of List.

If you cannot use an immutable collection, you could wrap the list in an annotated stable class in the simplest case to mark it as immutable for the Compose compiler. You most likely would want to create a generic wrapper for this though, based on your requirements.

You can then use this as the type of the parameter in your composable.

After taking either of these approaches, the HighlightedSnacks composable is now both skippable and restartable.

HighlightedSnacks will now skip recomposition when none of its inputs change.

The same issue also occurs with external libraries unless they are using the Compose compiler.

This is a known limitation and we are examining better solutions for multi-module architectures and external libraries currently.

No.

There was a lot of information in this blog post so let’s sum up.

Add a comment

Related posts:

How To Plan Your Time And Increase Your Success As An Affiliate Marketer

As an affiliate marketer, planning and time management are crucial to your success. Here are some strategies you can use to plan your time and increase your chances of success: By following these…

Types of bank accounts and their effect on financial investments

Investing in a diversified portfolio of securities is an important aspect of managing your finances and working towards financial goals. One of the ways to invest in securities is through a mutual…