Jetpack Compose: remember, mutableStateOf, derivedStateOf and rememberSaveable explained

Stefan M.
6 min readNov 25, 2022

--

If you have a composable function you might stumble over the following block of code. You might have even written it by yourself:

val stringState = remember { mutableStateOf("") }

Because I sometimes got confused about this “simple” line of code, I took some time to finally understand what it means, what it does, how it behaves, and how their siblings work.

General / Recomposition

When your UI widgets need to be updated, Compose will call those composable functions again. With the new or updated data. This behavior is called recomposition. Or in other words: The function gets recomposed.

And this makes absolute sense. Imagine you have a Text composable that should display the text that was entered in a TextField. Then, the text from the Text composable needs to be updated each time the TextField text changes, right? And how is this done? With recomposition.

To make it more clear. Here is a code snippet:

@Composable
fun TextFieldWithText() {
val stringState = remember { mutableStateOf("") }
TextField(
value = stringState.value,
onValueChange = { stringState.value = it }
)

Text(stringState.value)
}

If the stringState changes its value, the whole function (TextFieldWithText) will be called again. It will be recomposed!

This is the foundation behind Compose. More generally you can say:

Each time a state changes, Compose will recompose all composable functions which are affected by this state change.

remember

What happens if you call a normal function multiple times? The whole code block, anything which is inside the function, will be called again. This also means each variable will be newly created:

fun main() {
val namesList = listOf("StefMa", "juschmitt", "kip")
repeat(namesList.size) { number ->
greet(namesList[number])
}
}

fun greet(name: String) {
val isBlogAuthor = name == "StefMa"
if (isBlogAuthor) println("Hello $name. Awesome blog!")
else println("Hello $name")
}

The greet function is called three times. For each list entry once. The variable isBlogAuthor is therefore initialized each time the function gets called. The function will evaluate the condition name == "StefMa" each time greet is called.

This is, of course, the same behavior in Compose and what happens in case a function will be recomposed. But, you don’t necessarily want this right? And this is the time when remember enters the game.

Take the TextFieldWithText example from above. Imagine we wouldn’t wrap the mutableStateOf into remember. What would happen with the stringState if the function recompose? Exactly, it got initialized again. And therefore, the “new” stringState value in the second call is an empty string again!

So what is remember now? It saves the calculated return value (what happens inside the lambda) and returns this value each time a recomposition happens.

val stringState = remember { mutableStateOf("") }

What happens in this line of code?
remember calls the lambda only once — in the initial composition — and returns the same State<String> object each time a recomposition happens. In other words: It is always the same State<String> object which will be returned here.

You don’t have to return a State object inside remember. You can return anything which should not be computed again if a recomposition happens. For example, you could also calculate a random color inside which should be stable for recompositions.

remember fun fact:

You think remember sounds familiar to Kotlins lazy?
Well, it is the same. But on another “scope”. 😉
While lazy is bound to a class scope, remember is bound to a composable function.

In the Kotlin lazy docs it states:

The first call to get() executes the lambda passed to lazy() and remembers the result. Subsequent calls to get() simply return the remembered result.

Check the wording. It also uses “remember".

mutableStateOf

Halfway covered already in the General / Recomposition section above, but let’s make it clear again.

The function returns a MutableState object which leads to a recomposition in case its value changes and the value is read.

If the value is the same, nothing happens.
If the value isn’t read by anyone, nothing happens.
If the value changes, all composables that read this value will be recomposed.

And that’s basically it.

derivedStateOf

For me, this concept was the most complicated one to understand.

To understand it, I have a simple example that covers how it behaves. Not when to use it. I hope you understand when to use it after you know how it behaves 😉.

In this example, we have a Button that counts a number. If the number reaches 10 (the Button was pressed 10 times), we want to display an additional Text on the screen:

@Composable
private fun Counter() {
val counterState = remember { mutableStateOf(0) }

Button(
onClick = { counterState.value = counterState.value + 1 }
) {
Text(counterState.value.toString())
}

if (counterState.value >= 10) Text("Hurray!")
}

With our knowledge of the mutableState, we know that each time the Button got clicked, Counter will be recomposed. Reminder: This is because the value of the state changes which leads to a recomposition. And it is obvious why it needs to be recomposed. If the function wouldn’t be recomposed (called again by Compose) how will ever the if statement be reached, the condition evaluated and therefore the Text displayed?

On the other hand, we know that it shouldn’t be recomposed at least 9 times, right? Because the condition will not be satisfied as long as the counter doesn’t reach 10.

The recomposition is unnecessary for 9 times.

Thanks god there is derivedStateOf which does exactly this. It does not trigger a recomposition as long as the condition is not met. Or in other words, if the state changes. It kind of buffers the state and only emits if its changes.

So the new implementation would look like this:

@Composable
private fun CounterWithDerivedState() {
val counterState = remember { mutableStateOf(0) }

val showHurrayState = remember {
derivedStateOf { counterState.value > 10 }
}

Button(
onClick = { counterState.value = counterState.value + 1 }
) {
Text(counterState.value.toString())
}

if (showHurrayState.value) Text("Hurray!")
}

The showHurrayState will only change its value in case the counterState value is equal to or greater than 10. Before it always had the same value (false) and this doesn’t mean Compose shouldn’t recompose the function.

In a nutshell:
The calculation of the derivedStateOf will be called each time the States values inside the calculation lambda change. But this doesn’t trigger necessarily a state change of the DerivatedState in case it doesn’t have to change for the given State updates.

An alternative to derivedStateOf

We could also avoid recomposition without derivedStateOf. But first, let’s remember why our code without derivatedStateOf recomposed:
The reason is that the counterState changes its value and we read its value inside the composable.

The goal to avoid a recomposition is to avoid reading the state updates each time any update happens. We are only interested in a state update in case it reaches the number 10.

How can we achieve that?

What about introducing another MutableState whose value changes only in case the value of the counterState reaches 10?

@Composable
private fun CounterWithSecondMutableState() {
val counterState = remember { mutableStateOf(0) }
val showHurrayState = remember { mutableStateOf(false) }

Button(
onClick = {
counterState.value = counterState.value + 1
showHurrayState.value = counterState.value >= 10
}
) {
Text(counterState.value.toString())
}

if (showHurrayState.value) Text("Hurray!")
}

Compose will only recompose in case a State changes. Because the value of showHurrayState is false until the counterState.value reaches 10, the function needs only be recomposed once. At the time when we reach 10 with the counterState.

Note: Even if this works and behaves exactly what we want, this is not a recommended alternative by me! You should better use derivedStateOf instead. I only placed it here for demonstration purposes.

rememberSaveable

Are you still with me? Good! 😃
I also promise that this is a pretty quick one.

Did you ever hear of SavedInstanceState? I just assume you did. Well, rememberSaveable is the Compose pendant to it. It saves the calculated value into a Bundle (if it’s possible) on configuration change and can therefore restore it when the Activity or Fragment got recreated. For example on orientation change, split screen, or process death.

His sibling remember doesn’t do this by default. It will only save the computation for recompositions. rememberSaveable does this as well plus handling configuration changes.

We are done! I hope I could explain you the concepts and behaviors of those functions a bit better with easy examples. If not, don’t hesitate to comment on this blog.

--

--

Responses (8)