One Off the Slack: When Do We Use derivedStateOf()?

Steffen Funke asked:

Would derivedStateOf be the State-equivalent of combine in Flow / Rx-World? Meaning, if one of the inputs change, the resulting state should be recalculated? And can this also safely be used inside a ViewModel?

Background: I have e.g. a list and a search bar, and using MutableStateFlows inside my ViewModel for data streams. Whenever the search term changes, I combine the search term with the list, to filter it.

Works well and solid, but I think about going full Compose also in my ViewModels, therefore replacing my MutableStateFlows with Compose States.

Is this the correct approach, using derivedStateOf ?

Google’s Adam Powell responded:

derivedStateOf caches the result of a computation based on State accessed during that computation. However, you don’t need derivedStateOf to combine several different state inputs. It’s just as valid to do this:

class Greeter {
  var name by mutableStateOf("World")
  var greeting by mutableStateOf("Hello")

  val fullGreeting: String
    get() = "$greeting $name"

with no derivedStateOf at all; if you do Text(greeter.fullGreeting) then that will recompose any time either name or greeting change.

The key advantages of derivedStateOf are lazy evaluation and caching if the inputs have not changed. This caching isn’t free; if all you’re doing is something like the above, the computation isn’t particularly heavy, or you don’t have many reads of fullGreeting without inputs changing it’s better to keep it simple and just compute on the fly.

Steffen was suitably impressed. But, problems keep getting more complicated, so Steffen dug deeper:

when using derivedStateOf inside a ViewModel val, what would be the best strategy to let the calculation run inside a coroutine?

At the moment I am playing around with a quite an expensive filter logic. With flows, I was able to move that calculation to a background dispatcher with flowOn. I could still work around it by keeping this logic as a Flow, and then populating my mutableStateOfs from it - but I wonder if there is a better way.

Adam filled in the gaps:

Once you start wanting to compute derived things asynchronously then the tools get more complicated as well. You can use snapshotFlow to take a block of code that reads snapshot state and emits from a flow when things change, then do your usual redirection to other dispatchers if you like. At the end of a flow operator chain you can write another snapshot state value as part of your collect. This doesn’t come up often though, since snapshots are generally intended to be atomic. Offloading derived snapshot state calculations to another coroutine dispatcher means you’re probably giving up that atomicity unless you’re doing something pretty sophisticated by hand with the low level snapshot APIs

As for how it works, the current snapshot is a matter of some custom ThreadLocal tracking, so it doesn’t matter where in the call stack you are when a snapshot read or write is performed, it’s tracked.

The compose compiler plugin isn’t involved with snapshots at all

This is also a fine place to plug Zach Klippenstein’s long article on the snapshot system.

Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!