'Compose: remember() with keys vs. derivedStateOf()
What is the difference between these two approaches?
val result = remember(key1, key2) { computeIt(key1, key2) }
(Docs)val result by remember { derivedStateOf { computeIt(key1, key2) } }
(Docs)
Both avoid re-computation if neither key1
nor key2
has changed
.
The second also avoids re-computations if downstream states are derived, but else, they are identical in their behavior, aren't they?
Solution 1:[1]
AFAIK there is no difference here. It's just a coincidence that both constructs are doing the same thing here in this context. But, there are differences!
The biggest one is that derivedStateOf
is not composable and it does no caching on it's own (remember
does). So derivedStateOf
is meant for long running calculations that have to be run only if key changes. Or it can be used to merge multiple states that are not in composable (in custom class for example).
I think the exact explanation is blurred for "outsiders", we need some input from some compose team member here :). My source for the above is this one thread on slack and my own experiments
EDIT:
Today i learned another derivedStateOf
usage, very important one. It can be used to limit recomposition count when using some very frequently used value for calculation.
Example:
// we have a variable scrollState: Int that gets updated every time user scrolls
// we want to update our counter for every 100 pixels scrolled.
// To avoid recomposition every single pixel change we are using derivedStateOf
val counter = remember {
derivedStateOf {
(scrollState / 100).roundToInt()
}
}
// this will be recomposed only on counter change, so it will "ignore" scrollState in 99% of cases
Text(counter.toString()).
My source for that is as direct as it can be - from the author of compose runtime and the snapshot system, the Chuck Jazdzewski himself. I highly recommend watching stream with him here: https://www.youtube.com/watch?v=waJ_dklg6fU
EDIT2:
We finally have some official performance documentation with small mention of derivedStateOf
. So the official purpose of derivedStateOf
is to limit composition count (like in my example). sauce
Solution 2:[2]
val result = remember(key1, key2) { computeIt(key1, key2) }
re-calculates when key1
or key2
changes but derivedStateOf
is for tracking a change in one or more State/MutableState as stated in documents as
var a by remember { mutableStateOf(0) }
var b by remember { mutableStateOf(0) }
val sum = remember { derivedStateOf { a + b } }
// Changing either a or b will cause CountDisplay to recompose but not trigger Example
// to recompose.
CountDisplay(sum)
It's convenient to use derivedStateOf when you need to track a change in property of a State object. The value you store in State can be an object but when you need to track one or some properties of object you need to use derivedStateOf. And if it's not derived from a State/MutableState
or object with an interface with @Stable
annotation Composable won't recompose since recomposition requires a state change.
For instance an Input layout or number of items that you need to trigger another recomposition after a certain threshold or state.
var numberOfItems by remember {
mutableStateOf(0)
}
// Use derivedStateOf when a certain state is calculated or derived from other state objects.
// Using this function guarantees that the calculation will only occur whenever one
// of the states used in the calculation changes.
val derivedStateMax by remember {
derivedStateOf {
numberOfItems > 5
}
}
Column(modifier = Modifier.padding(horizontal = 8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Amount to buy: $numberOfItems", modifier = Modifier.weight(1f))
IconButton(onClick = { numberOfItems++ }) {
Icon(imageVector = Icons.Default.Add, contentDescription = "add")
}
Spacer(modifier = Modifier.width(4.dp))
IconButton(onClick = { if (derivedStateMin) numberOfItems-- }) {
Icon(imageVector = Icons.Default.Remove, contentDescription = "remove")
}
}
if (derivedStateMax) {
Text("You cannot buy more than 5 items", color = Color(0xffE53935))
}
}
This is whatsapp text input that displays icons based on whether text is empty or not by reading from text
internal fun ChatInput(modifier: Modifier = Modifier, onMessageChange: (String) -> Unit) {
var input by remember { mutableStateOf(TextFieldValue("")) }
val textEmpty: Boolean by derivedStateOf { input.text.isEmpty() }
Row(
modifier = modifier
.padding(horizontal = 8.dp, vertical = 6.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.Bottom
) {
ChatTextField(
modifier = modifier.weight(1f),
input = input,
empty = textEmpty,
onValueChange = {
input = it
}
)
Spacer(modifier = Modifier.width(6.dp))
FloatingActionButton(
modifier = Modifier.size(48.dp),
backgroundColor = Color(0xff00897B),
onClick = {
if (!textEmpty) {
onMessageChange(input.text)
input = TextFieldValue("")
}
}
) {
Icon(
tint = Color.White,
imageVector = if (textEmpty) Icons.Filled.Mic else Icons.Filled.Send,
contentDescription = null
)
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |