In the examples I find (here or here) I see that derivedStateOf
is always wrapped in a remember
block. Checking the Recomposition counts, I don't see a difference between
val foo = remember { derivedStateOf { someState } }
and
val foo = derivedStateOf { someState }
Could anyone show me an example where the result would differ?
Edit: So I got a different result in this example:
@Composable
fun Test() {
var count by remember {
mutableStateOf(0)
}
val moreThanOne = derivedStateOf {
Log.d("foo", "Calculate")
count > 1
}
Log.d("foo", "Read")
moreThanOne.value
Button(
onClick = {
Log.d("foo", "Clicked")
count += 1
}
) {
Text(text = "Increment")
}
}
Clicking the Button twice gives the following log:
Read
Calculate
Clicked
Calculate
Clicked
Calculate
Read
Calculate
Wrapping the derivedStateOf
with a remember
however logs:
Read
Calculate
Clicked
Calculate
Clicked
Calculate
Read
Still not completely sure why I see what I see.
Your code should look like,
@Composable
fun Test() {
var count by remember {
mutableStateOf(0)
}
val moreThanOne = remember {
derivedStateOf {
Log.d("foo", "Calculate")
count > 1
}
}
Log.d("foo", "Read")
moreThanOne.value
Button(
onClick = {
Log.d("foo", "Clicked")
count += 1
}
) {
Text(text = "Increment")
}
}
Note the additional remember
around derivedStateOf
. This doesn't affect the results of the logging but the lack of remember
around the derivedStateOf
requires additional overhead cause by the creating of a new derivedStateOf
every time Test
runs. You should always remember derivedStateOf
when in a composable function.
From this code I receive the following:
D/foo: Read
D/foo: Calculate
D/foo: Clicked
D/foo: Calculate
D/foo: Clicked
D/foo: Calculate
D/foo: Read
D/foo: Clicked
D/foo: Calculate
D/foo: Clicked
D/foo: Calculate
D/foo: Clicked
D/foo: Calculate
The first Read
and Calculated
are from initial composition. The value of count
is 0
.
After the first click Calculated
is emitted but nothing else. This is cause by Test
becoming conditionally invalidated by the change of count
. It is conditional in that Test
is only called if the calculated value of moreThanOne
is different than previous read by Test
. In this case it returns false
again so Test
is not called.
The second click Calculate
is seen again and now moreThanOne
produces true
which causes the conditional invalidation of Test
to be treated as a real invalidation and Test
is invoked to update the composition.
The third and subsequent clicks just produce Calculated
as the lambda is invoked (because count
changed) but the result of moreThanOne
is not different so the conditional invalidation of Test
is ignored.
The reason Calculated
appears before Read
even though the log of Read
is before the expression moreThanOne.value
is that the current value of moreThanOne
is updated prior to Test
being called to see if Test
needs to be called at all. If, however, Test
was invalidated for some other reason (that is, it read some other state object that changed) then moreThanOne
would only be updated by the call to moreThanOne.value
. Derived state objects are only updated prior to the call to Test
if they are the only reason Test
is being requested to be re-invoked. If they return the same value as the prior composition then the invalidation is ignored and the call is skipped.