State and derived state in Compose

Written by: Anders Järleberg

If you saw the video on performance best practices in Compose from Google I/O 2022, you saw the recommendation to use derivedStateOf when appropriate to reduce the number of recompositions that may be needed. Even while reading the documentation, it was not totally clear to me when and how it should be used over alternatives in general. After spending some time on researching it, I want to share my understanding of derivedStateOf in Compose and hope to provide a useful mental model for its function and use.

One of the fundamental parts of Compose is how it handles state (and updates to it) to produce our UI structure and contents, and it’s important to have the right understanding to produce correct and performant composables. Let’s start by looking at the basics of state in Compose, the use of remember, and finally how and when to use derivedStateOf.

State in Compose

The typical ways that Compose can know that a composable is dependent on a certain state are:

  • Passing a value as a parameter

  • Using a State variable

In both of these examples, whenever the state would change, Name would be called again and reflect the new state, thus emitting an updated UI. It’s important to keep in mind that this is actually code that executes, i.e. the Name function is executed again from the top. Unlike an XML UI, we’re not declaring objects once and then using them, we’re creating functions that depend on their input.

💡 In the rest of this article, when we read the value of a State, the .value will be omitted for brevity (as if we were using a delegated property).


Note that in the second example, I used a read-only state as input. Another common pattern you’ll see for (local) mutable state is

This is essentially the same case, Compose can see that the TextField is dependent on the value in the state of name (through Kotlin property delegation to the State with the by keyword). Let’s take a closer look at why the remember function is important.

The role of remember

Let’s say we’re showing a list of names and we want each name to have its own (random) color. Naïvely we might write the following:

This would appear to work, but one problem shows itself when we think about what happens if the name (or any other dependent state) changes, which would cause this function to recompose: we would generate a new random color. In this case that might be fine if the name has changed, but in reality there could be many reasons a composable is recomposed. To prevent the color from changing, we might use the remember function, as such:

The remember function provides us with what’s called positional memoization – we’re remembering this value at this specific position in the tree. When this function is recomposed (i.e. executed again), we have access to the value we defined previously at this position, so even when the name changes, the color does not.

However, this has another possibly unintended side effect. Let’s say we’re showing a row of three names: Alice (red), Bob (blue), and Carol (green). If we were to remove the first name in this list, we would end up with Bob (red) and Carol (blue), that is, the color is tied to the position and not the name itself.

The solution here is to generate the random color from the name and key the memoization to this as an input:

Note the name parameter passed to remember. The way to read this is that we want to remember the generated value at this position in the tree as long as the name is unchanged. The reason we have to generate the color from the name is that the memoization is still positional, so if the position changed as in the above example with Bob and Carol, we would have new colors unless the color is determined by the name. This also guarantees that if we use the Name composable in other parts of the tree we would have identical colors there for the same name.

You may be asking “if the color is determined by the name, why do we need to remember it?” In this case, it’s an optimization to skip evaluating the randomColorFor function every time we recompose (memoizing the value).

In the previous section I showed an example of a mutable state. Hopefully it’s clear to you why we needed to remember the MutableState (we shouldn’t create a new state “holder” every time), and why it wasn’t keyed to the value (we should keep the state holder around regardless of its current value).

Memoizing a calculation based on the correct set of input keys is important to the correctness and performance of a composable. This is essentially axiomatic in how Compose works, in the sense that if the input that a function takes is unchanged since last time we called it, we don’t need to execute it again (assuming no side effects).

Keyed remember vs derivedStateOf

This brings us to one of my initial questions: what is derivedStateOf and when should we use it over a keyed remember?

First, let’s look at what derivedStateOf actually does: it takes a lambda and returns a new state whose values will be the return value from your lambda. Reading from this state will not re-calculate the value, unless one of the states that your lambda depends upon has changed. What this means in practice might not be totally clear, so let’s look at some examples.

To begin with, let’s say we want a state that is based on two other states. What’s the difference between these two examples?

In both these cases, reading from myState will give us the combined state of A and B. Whenever the states of A or B change, myState will receive a new value. In this case, the two alternatives seem identical. The potential difference between these two actually lies in how myState will be used later on, not seen in this example. If we just read from myState directly in the same composable, there is no difference. However, let’s consider this case:

Here, we are not directly reading from myState in the same scope. Instead, we refer to it later on, and only conditionally so. Here is where one of the major differences becomes visible:

  1. In the case of remember(stateA, stateB), the combineStates function will unconditionally be called the first time and the value remembered. If either of stateA or stateB changes, the function will be called additionally every time. This happens regardless of the value of someCondition (i.e. regardless if we will read from myState). Additionally, when the value of myState does change (i.e. when A or B have changed), it will necessitate a recomposition of ItemsFromMyState and its child composables, since the local state has changed and the child (ultimately the LazyColumn) is dependent on it.

    You can compare this with the example used previously of passing a state as a parameter to a composable. We essentially have an invisible myState parameter passed all the way down to the LazyColumn‘s lambda where it will be used.

  2. In the case of derivedStateOf, nothing happens initially during composition of ItemsFromMyState until we reach the point where we would read from myState (i.e. inside the LazyColumn, if someCondition was true). At that point, we would call combineStates and store the returned value. Future reads of myState would not call the combineStates function, unless A or B have changed. If A or B changes, then any reads of myState in the same (or future) snapshots will include these changes (i.e. will have called combineStates with the updated values). Note that if someCondition was false, none of this would happen. Additionally, if combineStates does not produce a value different from the previous one, nothing else would happen. There is no recomposition of the composable or its children. If it does produce a different value, only the children that depend on myState are recomposed, i.e. in this case the LazyColumn.

Combining keyed remember and derived state

Of course, the two are not mutually exclusive. Derived state should be used when reading from States, but if the derived state is also dependent on non-State values, for correctness you must key the remember appropriately:

In the above example, we must key the remember to the value of input, as we will read it inside the derived state lambda. If we didn’t do this, we would keep remembering the old derived state if input changed, and it would not be recomputed since derivedStateOf is only notified of updates to States. This example also assumes that input would change less often than state. If the two would change equally often or if input changes more often than state, you could just use remember(input, state) instead.

Mental model for derived state

Derived state is its own State and should be treated as such, e.g. it needs to be remembered or stored in a ViewModel or similar.

derivedStateOf is like a lazy

The lambda you pass to derivedStateOf is only evaluated if it’s needed, and the value is memoized. However, unlike lazy, it can (and should) change in response to other states. When dependent states change and the value is read, the lambda will be called again with the updated values. The returned value may still be the same though, in which case nothing else happens.

derivedStateOf is like its own composable

You can think of the lambda you pass as its own composable scope, with dependent states and a resulting return value. When dependent states change, the lambda is called again, but it doesn’t recompose the surrounding composable(s) (unless they’re directly reading the same state). If your lambda returns the same value as last time, nothing else will be recomposed. If the value does change, only the composables that read the derived state need to recompose.

Derived state can be an optimization

As you may have seen in the IO session, the two guidelines above explain how we can optimize a state that depends on other states. One example is if you want a state that tells you if a list is scrolled to the top – instead of depending on the current scroll position directly (which will change very often and cause recompositions), you can depend on a derived state that tells you if the scroll position is at the top (whose resulting value only changes if the scroll reaches or leaves the top, thus causing fewer recompositions during a scroll):

That being said, it’s not a catch-all optimization you can always apply to reduce recompositions.

Derived state is not always an optimization

There are a few important things to keep in mind for derived state to actually improve performance, and to ensure correctness of your composable.

  • Your lambda generating the derived state must read from other States. If you are reading directly from a parameter, as far as Compose is concerned your composable is dependent on these values regardless of what you do in your lambda, e.g.


  • In the above example, if stateA or stateB changes, Inner is unconditionally recomposed (regardless of how we use myState). Even worse, since we’re remembering the derived state, it will remain unchanged as a and b change! The correct way to handle this here would be to either pass or refer to the States themselves in Inner, or to forego using derivedStateOf in favor of remember(a, b).

  • If you are reading your derived state in the same scope, there is no value to using derivedStateOf over a keyed remember if you’re already (unconditionally) reading all of its dependent states, e.g.

  • In the above example, we’re reading from stateA, stateB and myState in the same scope. This means if either of A or B change, the Foo composable will be recomposed anyway, and we would have been able to use a keyed remember. However, if myState was only conditionally read (or used further down the tree), that could still provide some benefit as described above (”lazy” evaluation).

As always when discussing optimization, measure first to determine if there is a problem. If you identify a problem, make sure you understand what’s happening and why. Hopefully these points have given you the mental model to understand in what situations derived state may be useful.

Conclusion

Generally speaking, it’s probably quite rare that you will need to reach for derivedStateOf in your Compose app, but in some specific scenarios it’s exactly what you want. Knowing how state works in Compose and thus knowing when and how to use the tools available to you for the optimal (and bug-free) behavior is always going to be of help in your daily work, and hopefully this post has given you some pointers along the way to deeper understanding of state in Compose.

Written by: Anders Järleberg

Anders has been developing Android apps at Bontouch for eight years, having dealt with both high and low in the Android ecosystem and beyond. When not coding, he also likes to share his learnings and learn from fellow developers.