Hi, I’m trying to understand the mutation policy p...
# compose
e
Hi, I’m trying to understand the mutation policy parameter of the
derivedStateOf
. The documentation says
Derived states without mutation policy trigger updates on each dependency change.
So I would basically expect
derivedStateOf
without policy parameter to behave like it behaves with
neverEqualPolicy.
But a simple test (a bit modified version of the documentation example):
Copy code
@Composable fun CountDisplay(count: State<Int>) {
    println("Count: ${count.value}")
}

@Composable fun Example() {
  var a by remember { mutableStateOf(0, neverEqualPolicy()) }
  var b by remember { mutableStateOf(0, neverEqualPolicy()) }
  val sum = remember { derivedStateOf { a + b } }

  CountDisplay(sum)
}
shows that it’s not true - changing either a or b to the same value triggers the recalculation, but does not trigger
CountDisplay
execution. Could anyone help me to understand what I’m missing here?
s
This only applies for nested derived states, observation in composition/measure/layout/draw apply their own default mutation policy, to allow the default use to be more ergonomic I'd love to have the default mutation policy to be structural in all places, but we can potentially break some people depending on it, as mutation policy parameter was added a year after release
To test it, use something like:
Copy code
val sum by remember { derivedStateOf { a + b } }
val nested by remember { derivedStateOf { sum.also { println("recalculated nested" } } }
If you read nested state, you'll see a lot of printlns
The
CountDisplay
won't be executed in any case however, as both states apply structural mutation policy by default when read in composition
If you specify the mutation policy, the state will use it everywhere, so you can control how the recalculated values are compared to each other.
e
Thanks, it’s clear now! Yep, I also noticed that when using derivedStateOf inside of another derivedStateOf, recalculation happens as stated in the documentation. But I didn’t know that composition / measure etc. observations have their own mutation policy, so could not understand the difference. That basically means we have to be very careful when using chains of derived states.
s
Practically, recomposition is way more expensive than a few derived state recalculations, unless they are deeply nested and used often
So it is a nice performance tip for those cases, or if you are building a library APIs exposing derived states somehow.
e
In my case the first derived state produces a data class and the second one produces a regular class without proper
equals
method override. I expected the second calculation to be executed when and only when the result of the first one changes, so did not care about equality. As a result, recomposition was triggered all the time.
s
Ah, right Do you need a second derived state then? You could just create a second class in composition?
e
Yes, but then it will be re-created when recomposition happens due to other changes. I was trying to optimize it with the second derivedStateOf but ended up with even worse solution 🙂
s
I think using mutation policy should solve the immediate problem here, but I'd reconsider, as using derived state for cases like this one could be more expensive than allocation
e
Ok, thank you! Anyway it was very interesting to find out why that happened even if it is not the final solution of the original issue.