StateFlow and SharedFlow
Reading Coroutine official guide thoroughly — Part 9
[Korean] English
When we develop application (especially for UI related), you would use STATE and EVENT inevitably. 😁
State
Let’s talk about the state which is dealt in a UI application.
- The state of user authentication can be one of the examples.
The state can be one of three states: “Logout”, “Login”, “Blocked”. - The state of presentational data can be another example.
The state can be declared like “Loading”, “DataAvailable”, “Error”, etc.
Usually an application declares necessary state machine and moves its state on the state machine as observing the UI modification or the result of business logic.
Especially, When you use MVVM architecture, normally UI related states are stored in a ViewModel (a.k.a VM) and the views bound to it react with the modification of the states.
In Kotlin, you can use enum class for the state that has self explained name and has no additional data. Otherwise, you can use sealed classes to include additional data in it.
In addition, LiveData is provided in Android framework in order to ease the observation of the states in a ViewModel from the View which has own lifecycle. However you should use another tool for the business layer since LiveData utilize framework (support) facilities and it means business (or domain) layer should also depends on the libraries.
In the past, I used to use RxJava/Kotlin — one of the implementation of Reactive Stream for Java/Kotlin — for this purpose. Especially, I used BehaviorSubject
because it has default state and can share its state to all the subscribers.
There are lots of Android applications choosing Kotlin as their main lanugage these days. When you use Kotlin in your project, you don’t have to depends on the RxJava/Kotlin anymore. There is light-weighted implementation of reactive stream called Flow in Kotlin Coroutine. Flow is basically ❄️ cold stream like other Observables in RX and you can see more detail in the Official Guide. For that reason, Flow create new stream every time when a collector collects from it. You can use StateFlow
or SharedFlow
to transform the ❄️ cold flow to 🔥 hot flow like Subject in RX. StateFlow has default value and share the most recent state to the collectors.
If you want to use Flow in the ViewModel of AndroidX, you can use viewModelScope
which is also provided by AndroidX and it is bound to the lifecycle of the ViewModel. It means that the viewModelScope will be cancelled when the viewModel get the callback onCleared()
.
Moreover, many libraries from AndroidX and 3rd parties start to support suspending functions and it makes Flow can be brighter than the others. One of the advantages of using Flows instead of Rx Observables is that you can use suspending function on the flow chain. This makes your code easy-to-read and easily sustainable.
Let’s take a look a sample code that uses StateFlow.
Let’s assume that refreshDataUseCase
sync recent data from server to local database. MainViewModel declares the StateFlow through line 6 ~ 8. _dataState
is a mutable StateFlow which can be used inside the ViewModel and dataState
is externally opened immutable StateFlow which is made from _dataState
through the helper function asStateFlow().
_dataState is read-write property
dataState is read-only property
When refreshData()
is called at line 11, a coroutine is created in the scope of ViewModel and it calls the suspending function of the UseCase. After that, when there is a result data come from the data layer, convert the result to relevant state class so that it can be emitted to UI. Eventually, UI layer collects the data and invalidate views.
Until now, we’ve covered about StateFlow briefly.
Then, What is the SharedFlow? 🙄
Event
As we’ve seen in the previous chapter, we declared a state machine when we develop an application and the application keeps changing its state on the state machine. There is one more thing we should focus on while we make our application. It’s EVENT.
A state machine has always its default state but a event doesn’t. An event is just triggered when the conditions declared are met.
Then, as a collector, what is the difference between the two?
- a STATE has its default value but a EVENT doesn’t
- a collector to a STATE will get the most recent value but a collector to an EVENT will get the event after it starts to collect.
The description above is about STATE and EVENT of UI application in general and the behavior can be changed as setting options provided by framework.
When can we use this EVENT in our application?
In general, you can use an EVENT to send events that are the result of interaction between a user and a view to a business layer. Or you can use it to send events that are related to warnings of system like lack of memory or unauthorized user.
Let’s take a look below example that uses SharedFlow.
Overall it looks similar to the example of StateFlow except it creates MutableSharedFlow
with some options.
- replay = 0 : Do not send old events to the new consumer.
- extraBufferCapacity = 1 : create additional buffer so that all the consumer can share new event.
- onBufferOverflow = BufferOverflow.DROP_OLDEST : remove the oldest data when buffer is full.
We can create the SharedFlow which is similar to PublishSubject in RX with those options.
Flow has a hierarchy like below.
Flow <- SharedFlow <- StateFlow.
A SharedFlow declares a replay cache and its size in order to set the count of values that can be transferred to new collectors. The collectors are managed as a form of Slot in the SharedFlow. When there are some values to transfer, all of the active collectors receive the values.
StateFlow inherits SharedFlow and has its own default value and overrides replaceCache as it makes the cache can have only one value.
We’ve seen 🔥 hot flows in coroutine till now. You can write your code simple and neat as using appropriate variants of hot flow in your project.
The end.