derivedStateOf
— a really common question we see is where and when is the correct place to use this API?
The answer to this question is derivedStateOf {}
should be used when your state or key is changing more than you want to update your UI. Or in other words, derivedStateOf
is like distinctUntilChanged
from Kotlin Flows or other similar reactive frameworks. Remember that Composables recompose when the Compose state object they read, changes. derivedStateOf
allows you to create a new state object that changes only as much as you need.
Let’s take a look at an example. Here we have a username field and a button that enables when the username is valid.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ var username by remember { mutableStateOf("") } val submitEnabled = isUsernameValid(username)
It starts off empty and so our state is false. Now when the user starts typing, our state correctly updates and our button becomes enabled.
But here is the problem, as our user keeps typing we are sending state to our button over and over again needlessly.
This is where derivedStateOf
comes in. Our state is changing more than we need our UI to update and so derivedStateOf
can be used for this to reduce the number of recompositions.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ var username by remember { mutableStateOf("") } val submitEnabled = remember { derivedStateOf { isUsernameValid(username) } }
derivedStateOf
Let’s go through the same example again to see the difference.
The user starts typing, but this time our username state is the only one that changes. The submit state just remains true. And of course, if our username becomes invalid. Our derived state correctly updates again.
Now, this example is a bit oversimplified. In a real app, Compose would most likely skip recomposition of the submit composable as its input parameters have not changed.
The reality is, the situations when you need derivedStateOf
can feel few and far between. But when you do find a case, it can be supremely effective at minimizing recomposition.
Always remember that there needs to be a difference in the amount of change between the input arguments and output result for
derivedStateOf
to make sense.
Some examples of when it could be used (not exhaustive):
- Observing if scrolling passes a threshold (scrollPosition > 0)
- Items in a list is greater than a threshold (items > 0)
- Form validation as above (username.isValid())
FAQs
Now, let’s look at some other commonly asked questions about derivedStateOf
.
Does derivedStateOf have to be remembered?
If it is inside a Composable function, yes. derivedStateOf
is just like mutableStateOf
or any other object that needs to survive recomposition. If you use it inside a composable function then it should be wrapped in a remember
or else it will be reallocated on every recomposition.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Composable fun MyComp() { // We need to use remember here to survive recomposition val state = remember { derivedStateOf { … } } } class MyViewModel: ViewModel() { // We don't need remember here (nor could we use it) because the ViewModel is // outside of Composition. val state = derivedStateOf { … } }
What’s the difference between remember(key) and derivedStateOf?
Remember with keys of each state and derivedStateOf
can seem quite similar at first glance.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ val result = remember(state1, state2) { calculation(state1, state2) } val result = remember { derivedStateOf { calculation(state1, state2) } }
The difference between remember(key)
and derivedStateOf
is in the amount of recomposition. derivedStateOf {}
is used when your state or key is changing more than you want to update your UI.
Take for example enabling a button only if the user has scrolled a LazyColumn
.
val isEnabled = lazyListState.firstVisibileItemIndex > 0
firstVisibleItemIndex
will change 0, 1, 2 etc as the user scrolls and causes readers to recompose every time it changes. We only care about if it’s greater than 0 or not. There is a difference in the amount of input we have and output we need and so derivedStateOf
is used here to buffer out that unnecessary recomposition.
val isEnabled = remember {
derivedStateOf { lazyListState.firstVisibleItemIndex > 0 }
}
Now, let’s say we have an expensive function that calculates something for us with a parameter. We want our UI to recompose any time the output of that function changes (importantly, the function is also idempotent). We use remember with a key here, as our UI needs to update just as much as our key changes. That is, we have the same amount of input and output.
val output = remember(input) { expensiveCalculation(input) }
Do I ever need to use remember(key) and derivedStateOf together? When is this needed?
This is where things get a little tricky. derivedStateOf
can only update when it reads a Compose state object. Any other variables read inside derivedStateOf
will capture the initial value of that variable when the derived state is created. If you need to use those variables in your calculation, then you can provide them as a key to your remember function. This concept is much easier to understand with an example. Let’s take our isEnabled
example from before and expand it to also have a threshold for when to enable the button, rather than 0.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Composable fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) { // There is a bug here val isEnabled by remember { derivedStateOf { lazyListState.firstVisibleItemIndex > threshold } } Button(onClick = { }, enabled = isEnabled) { Text("Scroll to top") } }
Here we have a button that enables when a list is scrolled over a threshold. We are correctly using derivedStateOf
to remove the extra recomposition, but there is a subtle bug. If the threshold parameter changes, our derivedStateOf
won’t take the new value into account as it captures the initial value on creation for any variable that isn’t a compose state object. As threshold is an Int
, whatever is the first value that is passed into our composable will be captured and used for the calculation from then on. ScrollToTopButton
will still recompose, as its inputs have changed, but as remember without any keys caches across recomposition, it will not reinitialise the derivedStateOf
with the new value.
We can see this by looking at the outputs from our code. At first everything is working correctly.

But then a new value (5)
for threshold is passed into our composable.

Even though our scrollPosition
is less than threshold
, isEnabled
is still set to true
.
The fix here is to add threshold as a key for remember, this will reinitialise our derivedStateOf
state anytime threshold changes.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ val isEnabled by remember(threshold) { derivedStateOf { lazyListState.firstVisibleItemIndex > threshold } }
Now we can see, when the threshold changes, the isEnabled
state correctly updates.

Do I need to use derivedStateOf to combine multiple states together?
Most likely, no. If you have multiple states that are combining together to create a result then you probably want recomposition to happen any time one of them changes.
Take for example a form that takes in a first name and last name and displays a full name.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullName = "$firstName $lastName"
Here, as our output changes just as much as our input, derivedStateOf is not doing anything and is just causing a small overhead. derivedStateOf also isn’t helping with asynchronous updates, the Compose state snapshot system is dealing with that separately and these calls here are synchronous.
In this case, there is no need for an extra derived state object at all.
/* Copyright 2022 Google LLC. SPDX-License-Identifier: Apache-2.0 */ var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullName = "$firstName $lastName"
Conclusion
To sum up, remember that derivedStateOf is used when your state or key is changing more than you want to update your UI. If you don’t have a difference in the amount of input compared with output, you don’t need to use it.
By Ben Trengrove
Source Android