Skip to content

Reactive programming

val number = MutableStateFlow(0)

// For complex apps you often need reactive StateFlows. That's what derived() is for.
// This StateFlow is automatically recomputed whenever number's value is changed
val doubledNumber: StateFlow<Int> = derived { 2 * get(number) }

// Here we only compute the value while someone is subscribed to changes (autoRun,
// derived or collect). This can be important for expensive computations/operations.
val onDemandDoubledNumber = derived(initial = 0, started = WhileSubscribed()) {
    // Note: you could even call suspension functions from within this block
    // to e.g. fetch something from the backend.
    2 * get(number)
}

With autoRun (available on LifecycleOwner, ViewModel, CoroutineScope, etc.) you can observe and re-execute a function whenever any of the StateFlow or LiveData instances accessed by that function are modified. On Android you can use this to keeping the UI in sync with your ViewModel. Of course, you can also keep non-UI state in sync. Depending on the context in which autoRun is executed, this observer is automatically tied to a CoroutineScope (e.g. the ViewModel’s viewModelScope) or in case of a Fragment/Activity to the onStart()/onStop() lifecycle in order to prevent accidental crashes and unnecessary resource consumption.

With derived you can construct new StateFlows based on the autoRun principle. You can control when the calculation should run by passing Eagerly, Lazily or WhileSubscribed(), for example. Especially WhileSubscribed() is important for expensive computations.

If you don’t have access to a CoroutineScope you can use the non-suspend based derived and suspend-based derivedWhileSubscribed. However, in this case you can’t use Eagerly/Lazily/etc. since that would prevent garbage collection. The non-suspend derived recomputes itself when accessing its .value and while someone is subscribed. The suspend-based derivedWhileSubscribed only recomputes while someone is subscribed. Since it might be important if you rely on side-effects (which is not a good idea anyway), it should be emphasized: Both of these CoroutineScope-less functions have in common that they won’t do any recomputation if nobody is subscribed and nobody accesses .value. If you want the full Eager/Lazy behavior you need a CoroutineScope.

Note that autoRun can be extended to support observables other than StateFlow, LiveData and WhileUsed.

The simplicity advantage of autoRun/derived requires using StateFlow instead of Flow to avoid writing chains of combine, map, flatMapLatest, conflate, etc. Luckily, if you only have a Flow, you can use Flow.stateIn() to convert it to a StateFlow.