Things to know about Flow’s shareIn and stateIn operators
Flow.stateIn operators convert cold flows into hot flows: they can multicast the information that comes from a cold upstream flow to multiple collectors. They’re often used to improve performance, add a buffer when collectors are not present, or even as a caching mechanism.
Note: Cold flows are created on-demand and emit data when they’re being observed. Hot flows are always active and can emit data regardless of whether or not they’re being observed.
In this blog post, you’ll become familiar with the
stateIn operators by example. You’ll learn how to configure them to perform certain use cases and avoid common pitfalls you might encounter.
The underlying flow producer
Continuing with the example from my previous blog post, the underlying flow producer that we’re using emits location updates. It’s a cold flow, as it’s implemented using a
callbackFlow. Every new collector will trigger the flow producer block, and a new callback will be added to the
Let’s see how we can use the
stateIn operators to optimize the
locationsSource flow for different use cases.
shareIn or stateIn?
Note: To learn more about
SharedFlow, check out our documentation.
StateFlow is a specialized configuration of
SharedFlow optimized for sharing state: the last emitted item is replayed to new collectors, and items are conflated using
Any.equals. You can read more about this in the
The main difference between these APIs is that the
StateFlow interface allows you to access the last emitted value synchronously by reading its
value property. That’s not the case with
These APIs can improve performance by sharing the same instance of the flow to be observed by all collectors instead of creating new instances of the same flow on-demand.
In the following example,
LocationRepository consumes the
locationsSource flow exposed by the
LocationDataSource and applies the shareIn operator to make everyone interested in the user’s location collect from the same instance of the flow. Only one instance of the
locationsSource flow is created and shared for all collectors:
WhileSubscribed sharing policy is used to cancel the upstream flow when there are no collectors. In this way, we avoid wasting resources when no one is interested in location updates.
Tip for Android apps! You can use
WhileSubscribed(5000)most of the time to keep the upstream flow active for 5 seconds more after the disappearance of the last collector. That avoids restarting the upstream flow in certain situations such as configuration changes. This tip is especially helpful when upstream flows are expensive to create and when these operators are used in ViewModels.
For this example, our requirements have changed, and now we’re asked to always listen for location updates and display the last 10 locations on the screen when the app comes from the background:
We use a
replay value of 10 to keep the last 10 emitted items in memory and re-emit those every time a collector observes the flow. To keep the underlying flow active all the time and emitting location updates, use the
SharingStarted.Eagerly policy to listen for updates even if there are no collectors.
Our requirements have changed again, and in this case, we don’t need to be always listening for location updates if the app is in the background. However, we need to cache the last emitted item so that the user always sees some data on the screen, even if stale, while getting the current location. For this case, we can use the
Flow.stateIn caches and replays the last emitted item to a new collector.
WATCH OUT! Do not create new instances on each function call
stateIn to create a new flow that’s returned when calling a function. That’d create a new
StateFlow on each function invocation that will remain in memory until the scope is cancelled or is garbage collected when there are no references to it.
Flows that require input
Flows that require input, like a
userId, cannot be shared easily using
stateIn. Taking as an example the iosched open-source project — Google I/O’s Android app — the flow to get user events from Firestore is implemented using a
callbackFlow, as you can see in the source code. As it takes the
userId as a parameter, this flow cannot be reused easily using the
Optimizing this use case depends on the requirements of your app:
- Do you allow receiving events from multiple users at the same time? You might need to create a map of
StateFlowinstances, and remove the reference and cancel the upstream flow when the
- If you allow only one user, and all collectors need to update to the new user, you could emit event updates to a common
StateFlowfor all collectors and use the common flow as a variable in the class.
stateIn operators can be used with cold flows to improve performance, add a buffer when collectors are not present, or even as a caching mechanism! Use them wisely, and don’t create new instances on each function call — it won’t work as you’d expect!