SharedFlow and StateFlow
Flow is typically cold, so its values are calculated on demand. However, there are cases in which we want multiple receivers to be subscribed to one source of changes. This is where we use SharedFlow, which is conceptually similar to a mailing list. We also have StateFlow, which is similar to an observable value. Let's explain them both step by step.
Let's start with
MutableSharedFlow, which is like a broadcast channel: everyone can send (emit) messages which will be received by every coroutine that is listening (collecting).
The above program never ends because the
coroutineScopeis waiting for the coroutines that were started with
launchand which keep listening on
MutableSharedFlowis not closable, so the only way to fix this problem is to cancel the whole scope.
MutableSharedFlow can also keep sending messages. If we set the
replay parameter (it defaults to 0), the defined number of last values will be kept. If a coroutine now starts observing, it will receive these values first. This cache can also be reset with
MutableSharedFlowis conceptually similar to RxJava Subjects. When the
replayparameter is set to 0, it is similar to a
replayis 1, it is similar to a
Int.MAX_VALUE, it is similar to
In Kotlin, we like to have a distinction between interfaces that are used to only listen and those that are used to modify. For instance, we've already seen the distinction between
ReceiveChannel and just
Channel. The same rule applies here.
MutableSharedFlow inherits from both
FlowCollector. The former inherits from
Flow and is used to observe, while
FlowCollector is used to emit values.
These interfaces are often used to expose only functions, to emit, or only to collect.
Here is an example of typical usage on Android:
Flow is often used to observe changes, like user actions, database modifications, or new messages. We already know the different ways in which these events can be processed and handled. We've learned how to merge multiple flows into one. But what if multiple classes are interested in these changes and we would like to turn one flow into multiple flows? The solution is
SharedFlow, and the easiest way to turn a
Flow into a
SharedFlow is by using the
shareIn function creates a
SharedFlow and sends elements from its
Flow. Since we need to start a coroutine to collect elements on flow,
shareIn expects a coroutine scope as the first argument. The third argument is
replay, which is 0 by default. The second argument is interesting:
started determines when listening for values should start, depending on the number of listeners. The following options are supported:
SharingStarted.Eagerly- immediately starts listening for values and sending them to a flow. Notice that if you have a limited
replayvalue and your values appear before you start subscribing, you might lose some values (if your replay is 0, you will lose all such values).
SharingStarted.Lazily- starts listening when the first subscriber appears. This guarantees that this first subscriber gets all the emitted values, while subsequent subscribers are only guaranteed to get the most recent replay values. The upstream flow continues to be active even when all subscribers disappear, but only the most recent replay values are cached without subscribers.
WhileSubscribed()- starts listening on the flow when the first subscriber appears; it stops when the last subscriber disappears. If a new subscriber appears when our
SharedFlowis stopped, it will start again.
WhileSubscribedhas additional optional configuration parameters:
stopTimeoutMillis(how long to listen after the last subscriber disappears, 0 by default) and
replayExpirationMillis(how long to keep replay after stopping,
- It is also possible to define a custom strategy by implementing the
shareIn is very convenient when multiple services are interested in the same changes. Let's say that you need to observe how stored locations change over time. This is how a DTO (Data Transfer Object) could be implemented on Android using the Room library:
The problem is that if multiple services need to depend on these locations, then it would not be optimal for each of them to observe the database separately. Instead, we could make a service that listens to these changes and shares them into
SharedFlow. This is where we will use
shareIn. But how should we configure it? You need to decide for yourself. Do you want your subscribers to immediately receive the last list of locations? If so, set
replay to 1. If you only want to react to change, set it to 0. How about
WhileSubscribed() sounds best for this use case.
Beware! Do not create a new SharedFlow for each call. Create one, and store it in a property.
StateFlow is an extension of the SharedFlow concept. It works similarly to SharedFlow when the
replay parameter is set to 1. It always stores one value, which can be accessed using the
Please note how the
valueproperty is overridden inside
MutableStateFlow. In Kotlin, an
open valproperty can be overridden with a
valonly allows getting a value (getter), while
varalso supports setting a new value (setter).
The initial value needs to be passed to the constructor. We both access and set the value using the
value property. As you can see,
MutableStateFlow is like an observable holder for a value.
On Android, StateFlow is used as a modern alternative to LiveData. First, it has full support for coroutines. Second, it has an initial value, so it does not need to be nullable. So, StateFlow is often used on ViewModels to represent its state. This state is observed, and a view is displayed and updated on this basis.
Beware that StateFlow is conflated, so slower observers might not receive some intermediate state changes. To receive all events, use SharedFlow.
This behavior is by design. StateFlow represents the current state, and we might assume that nobody is interested in an outdated state.
stateIn is a function that transforms
StateFlow<T>. It can only be called with a scope, but it is a suspending function. Remember that StateFlow needs to always have a value; so, if you don't specify it, then you need to wait until the first value is calculated.
The second variant of
stateIn is not suspending but it requires an initial value and a
started mode. This mode has the same options as
shareIn (as previously explained).
We typically use
stateIn when we want to observe a value from one source of changes. On the way, these changes can be processed, and in the end they can be observed by our views.
In this chapter, we've learned about
StateFlow, both of which are especially important for Android developers as they are commonly used as a part of the MVVM pattern. Remember them and consider using them, especially if you use view models in Android development.