StateFlow
StateFlow
is described as a flow that represents a read-only state with a single updatable data value that generates value updates to its flow collectors. StateFlow
is a useful data-model to represent any kind of state, it is designed to be used in situations where state management is required in asynchronous execution with Kotlin Coroutines. StateFlow
is raised to stable API since Kotlin Coroutines 1.4.0
version Kotlin Coroutines 1.4.0
StateFlow comes in two variations: StateFlow and MutableStateFlow :
StateFlow<T>
interface provides only value access.MutabaleStateFlow<T>
interface provides the ability to modify values.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">public</span> <span class="token keyword">interface</span> StateFlow <span class="token operator"><</span> <span class="token keyword">out</span> T <span class="token operator">></span> <span class="token operator">:</span> SharedFlow <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">val</span> value <span class="token operator">:</span> T <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> MutableStateFlow <span class="token operator"><</span> <span class="token keyword">out</span> T <span class="token operator">></span> <span class="token operator">:</span> StateFlow <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">,</span> MutableSharedFlow <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token keyword">var</span> value <span class="token operator">:</span> T <span class="token keyword">public</span> <span class="token keyword">fun</span> <span class="token function">compareAndSet</span> <span class="token punctuation">(</span> <span class="token keyword">expect</span> <span class="token operator">:</span> T <span class="token punctuation">,</span> update <span class="token operator">:</span> T <span class="token punctuation">)</span> <span class="token operator">:</span> Boolean <span class="token punctuation">}</span> |
StateFlow
‘s StateFlow
is denoted by value through the value
property. The value
property always has an initial value so it can be safely read at any time. Any updates to the value will be reflected in all receivers by emitting a value with status updates. In Android, StateFlow
great for classes that need to maintain an observable state of change.
A simple example of how to use State flow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">class</span> MainViewModel <span class="token operator">:</span> <span class="token function">ViewModel</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">val</span> _countState <span class="token operator">=</span> <span class="token function">MutableStateFlow</span> <span class="token punctuation">(</span> <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token keyword">val</span> countState <span class="token operator">:</span> StateFlow <span class="token operator"><</span> Int <span class="token operator">></span> <span class="token operator">=</span> _countState <span class="token keyword">fun</span> <span class="token function">incrementCount</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> viewModelScope <span class="token punctuation">.</span> <span class="token function">launch</span> <span class="token punctuation">{</span> _countState <span class="token punctuation">.</span> value <span class="token operator">++</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">fun</span> <span class="token function">decrementCount</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> viewModelScope <span class="token punctuation">.</span> <span class="token function">launch</span> <span class="token punctuation">{</span> _countState <span class="token punctuation">.</span> value <span class="token operator">--</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
This snippet is quite similar to the usage of LiveData
:
- Declare
_countState
as MutableStateFlow to initialize value and provide ability to update value. - Declare an instance of StateFlow as the
countState
exposed to the Views ( read-only field ).
When the receiver begins to collect values from StateFlow
, the receiver gets the last state in the stream and any subsequent states. The values in StateFlow
are combined using the Any.equals comparison in a similar way to the distinctUntilChanged
operator. It is used to incorporate value
updates in a MutableStateFlow and to prevent outputting values to the receiver when the new value is equal to the previously emitted value.
Example views that listen to StateFlow
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <span class="token keyword">class</span> MainActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">val</span> viewModel <span class="token keyword">by</span> lazy <span class="token punctuation">{</span> <span class="token function">ViewModelProvider</span> <span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">)</span> <span class="token punctuation">[</span> MainViewModel <span class="token operator">::</span> <span class="token keyword">class</span> <span class="token punctuation">.</span> java <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreate</span> <span class="token punctuation">(</span> savedInstanceState <span class="token operator">:</span> Bundle <span class="token operator">?</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">onCreate</span> <span class="token punctuation">(</span> savedInstanceState <span class="token punctuation">)</span> <span class="token function">setContentView</span> <span class="token punctuation">(</span> R <span class="token punctuation">.</span> layout <span class="token punctuation">.</span> activity_main <span class="token punctuation">)</span> button_plus <span class="token punctuation">.</span> <span class="token function">setOnClickListener</span> <span class="token punctuation">{</span> viewModel <span class="token punctuation">.</span> <span class="token function">incrementCount</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> button_minus <span class="token punctuation">.</span> <span class="token function">setOnClickListener</span> <span class="token punctuation">{</span> viewModel <span class="token punctuation">.</span> <span class="token function">decrementCount</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> viewLifecycleOwner <span class="token punctuation">.</span> lifecycleScope <span class="token punctuation">.</span> <span class="token function">launchWhenStarted</span> <span class="token punctuation">{</span> viewModel <span class="token punctuation">.</span> countState <span class="token punctuation">.</span> <span class="token function">collect</span> <span class="token punctuation">{</span> value <span class="token operator">-></span> textview_count <span class="token punctuation">.</span> text <span class="token operator">=</span> <span class="token string">" <span class="token interpolation variable">$value</span> "</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The example uses viewLifecycleOwner.lifecycleScope.launchWhenStarted
to bind to the lifecycle of the views.
StateFlow and LiveData
StateFlow
and LiveData
have similarities. Both are classes of observable data, and both follow a similar pattern when used in application architecture. However, StateFlow
and LiveData
have different behaviors:
StateFlow
requires an initial state passed into the constructor whileLiveData
does not.StateFlow
preventsStateFlow
of values to a receiver when a new value is equal to a previously issued value.LiveData.observe()
automatically unsubscribes consumers when the UI switches toSTOPPED
, while collect from aStateFlow
or any other stream does not.
In addition, StateFlow
can use many good operators of Flow (zip, combine …) while LiveData
very limited.
In the example above using viewLifecycleOwner.lifecycleScope.launchWhenStarted
to realize the lifecycle when collecting from flow, to avoid wasting resources. Alternatively, handling when changing the Lifecycle states manually is also possible, as shown in the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">class</span> MainActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span> <span class="token punctuation">.</span> <span class="token keyword">private</span> <span class="token keyword">var</span> uiStateJob <span class="token operator">:</span> Job <span class="token operator">?</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onStart</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">onStart</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> uiStateJob <span class="token operator">=</span> lifecycleScope <span class="token punctuation">.</span> <span class="token function">launch</span> <span class="token punctuation">{</span> viewModel <span class="token punctuation">.</span> uiState <span class="token punctuation">.</span> <span class="token function">collect</span> <span class="token punctuation">{</span> uiState <span class="token operator">-></span> <span class="token comment">// Handle UI state</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onStop</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> uiStateJob <span class="token operator">?</span> <span class="token punctuation">.</span> <span class="token function">cancel</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">onStop</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Another way to stop listening for uiState
changes when the UI isn’t showing is to use the asLiveData()
function in the lifecycle-livedata-ktx
library to convert the flow back to LiveData
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> LatestNewsActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span> <span class="token punctuation">.</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreate</span> <span class="token punctuation">(</span> savedInstanceState <span class="token operator">:</span> Bundle <span class="token operator">?</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span> <span class="token punctuation">.</span> viewModel <span class="token punctuation">.</span> uiState <span class="token punctuation">.</span> <span class="token function">asLiveData</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">observe</span> <span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">-></span> <span class="token comment">// Handle UI state</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Refer
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
https://scalereal.com/android/2020/05/22/stateflow-end-of-livedata.html
https://cesarmorigaki.medium.com/replace-livedata-with-stateflow-4f3c89214b04