Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

derivedStateOf vs remember with key and MutableState

Is there any difference between the following two functions:

First function with derivedStateOf:

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

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

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

    if (showFinallyState.value) Text("Finally!")
}

Second function with remember, key and MutableState:

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

    val showFinallyState by remember(counterState) {
        mutableStateOf(counterState.value > 10)
    }

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

    if (showFinallyState) Text("Finally!")
}

I would expect the same behavior in terms of recomposition.

like image 411
nature8 Avatar asked Aug 31 '25 23:08

nature8


1 Answers

Edit:
As it turned out, the information provided at the sources (#1 #2) I was basing my answer upon were were not completely accurate. The answer below includes my latest findings made using the Layout Inspector.

Summary: Both approaches result in the same amount of recompositions.

Please check out the documentation of remember:

Remember the value returned by calculation if key1 compares equal (==) to the value it had in the previous composition, otherwise produce and remember a new value by calling calculation.

If you use the declaration

val showFinallyState by remember(counterState) {
    mutableStateOf(counterState.value > 10)
}

then the value of showFinallyState will be recomputed each time the counterState changes. However, when the output of the calculation actually has not changed from the previous value, the Composables depending on showFinallyState will not recompose. That means that the first recomposition will happen after counterState is > 10.

Now let's check check the documentation of derivedStateOf:

You should use the derivedStateOf function when your inputs to a Composable are changing more often than you need to recompose.

In your first code snippet

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

you will only get a recomposition of all Composables depending on showFinallyState once the counterState actually is > 10.

So actually, the two approaches result in the same amount of recompositions in dependent Composables.


I used the following Composable to investigate the cause:

@Composable
fun SampleComposable() {

    val counterState = remember { mutableStateOf(0) }

    val rememberKeyVariable = remember(counterState.value) {
        mutableStateOf(counterState.value > 10)
    }

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

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        Button(
            onClick = { counterState.value = counterState.value + 1 }
        ) {
            Text("INCREASE COUNTER")
        }
        Text(text = "rememberKeyVariable: ${rememberKeyVariable.value}")
        Text(text = "derivedStateVariable: ${derivedStateVariable.value}")
    }
    
}

This is the output of the Layout Inspector after clicking the Button eleven times:

Layout Inspector

In the Layout Inspector, we can see that both Text Composables actually can skip ten recompositions, until finally the counterState is > 10, at which point they recompose.

Feel free to comment in case you find any flaws with the setup of the experiment.

like image 124
BenjyTec Avatar answered Sep 03 '25 14:09

BenjyTec