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
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
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
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
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:
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:
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
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).
This brings us to one of my initial questions: what is
derivedStateOf and when should we use it over a keyed
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:
- In the case of
remember(stateA, stateB), the
combineStatesfunction will unconditionally be called the first time and the value remembered. If either of
stateBchanges, 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
myStatedoes change (i.e. when A or B have changed), it will necessitate a recomposition of
ItemsFromMyStateand 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
myStateparameter passed all the way down to the
LazyColumn‘s lambda where it will be used.
- In the case of
derivedStateOf, nothing happens initially during composition of
ItemsFromMyStateuntil we reach the point where we would read from
myState(i.e. inside the
someConditionwas true). At that point, we would call
combineStatesand store the returned value. Future reads of
myStatewould not call the
combineStatesfunction, unless A or B have changed. If A or B changes, then any reads of
myStatein the same (or future) snapshots will include these changes (i.e. will have called
combineStateswith the updated values). Note that if
someConditionwas false, none of this would happen. Additionally, if
combineStatesdoes 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
myStateare recomposed, i.e. in this case the
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
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
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.
- If you are reading your derived state in the same scope, there is no value to using
derivedStateOfover a keyed
rememberif you’re already (unconditionally) reading all of its dependent states, e.g.
In the above example, if stateA or stateB changes,
Inneris unconditionally recomposed (regardless of how we use
myState). Even worse, since we’re remembering the derived state, it will remain unchanged as
bchange! The correct way to handle this here would be to either pass or refer to the
States themselves in Inner, or to forego using
derivedStateOfin favor of
In the above example, we’re reading from
myStatein the same scope. This means if either of A or B change, the
Foocomposable will be recomposed anyway, and we would have been able to use a keyed
remember. However, if
myStatewas 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.
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.