This text is based on 1.3.7 version of kotlinx.coroutines library
Welcome to the last (at least, for now) article of this series, where we are going to discuss what is a StateFlow and its behaviour.
You can see some examples by checking the GitHub Repository. There, you will see how a StateFlow is implemented and how it works. Now that you will have a basis about Kotlin Streams API, you can fork and modify all the examples that you have there 😃
Remember that this article belongs to a series:
- Part 1: Cold & Hot Streams.
- Part 2: Flows.
- Part 3: Channels.
- Part 4: BroadcastChannels.
- Part 5: StateFlows and conclusions.
Let’s go with part five: StateFlows!
StateFlows
What is a StateFlow?
We know what Flows, Channels and BroadcastChannels are, and we have all the stream types implemented within them. So, what's a StateFlow?
Well, basically it's a new flow implementation, which is designed to make a flow stateful, to keep an updatable state over time. With this, we are saying that it can have a state and it doesn't depend on a specific context, like a normal flow implementation.
As a Flow, StateFlow is an interface too. Also, we have a mutable version of StateFlow, represented by the MutableStateFlow interface.
We can see that actually a StateFlow is a Flow with a value. The MutableStateFlow makes that value mutable.
Creating StateFlows
A StateFlow cannot be created directly, because it’s an interface and we cannot instantiate an interface.
Instead, there is a factory function (yes, factory functions for the win) to create a MutableStateFlow.
And that's it!
Exposing the StateFlow
One of the main advantages of a StateFlow is that it can be exposed in two different modes, with only one instance.
Reactive mode
If we need to access the value in a reactive way, it can be exposed as a Flow. So if we want, for example, to collect all the changes to the StateFlow's value, we can collect them in the same way we did with Flows.
Non-Reactive Mode
If we need to get the value of the StateFlow directly, in a non-reactive way, as it exposes its value, we can access just by calling its getter.
Emitting and Updating values
As we can imagine, an update to StateFlow's value triggers an emission of the Flow.
How does StateFlow notifies its listeners that its value has changed? Well, all the updates are performed in a Conflated way: it works like a Conflated Channel, where the value in the buffer is always the latest sent to it, and the values that weren't received by someone got lost.
So, a slow collector will omit an update that happens faster than the processing of the previous value. But, as it's a conflated emission, the collectors will always have the last value emitted.
Maybe this mechanism of emissions is one of the reasons why the StateFlow is intended to replace the ConflatedBroadcastChannel. It emits like a Conflated Channel, and anyone who has a reference to the StateFlow can read its value, so it has a multicast feature.
How to change StateFlow's value? Just modifying it with the setter:
What's the difference with Flows?
There are several differences that we can mention. The most important ones are:
- Multicast feature: it can have more than one collector, and it can emit the value to all of them.
- Non-reactive Flow: we can access the StateFlow's value without performing a collection. And we can access to it as fast as we want, because we don't have to wait until the next emission.
- Emit from wherever: we can modify the StateFlow's value using its mutable version just by having its reference. So, if we have the reference, we can emit, simple as that.
- Scope: StateFlow doesn't have an execution context by itself. If we remember, the Flow scope is the CoroutineScope that triggers its execution. Here we don't have that constraint (or benefit?).
What's the difference with ConflatedBroadcastChannels?
Also, there are several differences with that we can mention, even if they're too similar (because, conceptually, they're the same). For me, the most important ones are:
- The Flow API is simpler to use than the Channel API, so we have a component that works just like a Channel, but with a much more simpler and powerful API.
- The StateFlow always has a value, unlike a Channel. We cannot instantiate a StateFlow without a value.
- We have a clear mutable/immutable separation. With Channels, anyone that has its reference can send values, but with StateFlows we can expose the immutable version to the outside and no-one will perform an emission, because they won't be able to do it.
- StateFlows emit based on equality, Channels do it based on reference. This means that if we emit the same value (not the same object), that value will be omitted. To get more information about how equality works, check this link.
- The StateFlow cannot be closed or represent a failure, because the API doesn't allow it. That's a huge difference with Channels, where we can just cancel or close them.
Using the StateFlow
There isn't too much to say here, because it inherits the Flow interface to collect the values emitted, so we can use it as a Flow. Anyway, I will mention some stuff that you've to consider when using the StateFlow:
- StateFlow has a last value, so we'll always receive at least one emission.
- The emission can be activated before someone subscribes to the StateFlow.
- We have a multicast stream.
And that's all for StateFlows!
Conclusions
After five articles, I don't want to lose the chance of giving my opinion about the different APIs and which of them are more convenient to use.
First of all, I think that these APIs are very helpful for almost all the use cases that require some reactive flows. With all the components that we've reviewed, we can be sure that, if we want, we can implement any stream flow in our applications or systems. So, good for us that we can depend only on the programming language to do this stuff!
Second, I think that you've already noticed that there is no such cold/hot concern separation. Furthermore, in my opinion, it's not so important to know if you have to choose a cold or a hot stream, instead, you must know how the Kotlin APIs work and then decide which of them will help you fulfilling your goal. The most important things to understand are the purposes and the behaviours, and not the definitions.
Lastly, if you're developing for the Android platform, I'll recommend you to avoid Channels API. You can use Flow and StateFlow to develop almost any feature, and they're much more simpler to use than Channels. So, if you are thinking about implementing some Channel's feature, be sure that StateFlow it's not what you need.
The end
So, we are in the last section of the last article. Thank you all for reading, sharing and clapping the articles on this series.
I really enjoy writing them, and I really hope that they will be helpful and useful for all the developers who are diving in the world of the streams.
I'll be trying to stay up to date with the new kotlinx coroutines libraryversions, and to update the articles according to those releases.
Thanks again and see you later!
Special thanks to the MediaMonks Android team that gives feedback.