Atomic Updates on MutableStateFlow
<p>I’m going to describe this problem in the context of Android, since that’s the platform I most commonly work on. But this issue is 100% Kotlin so it could easily apply to other platforms.</p>
<p><code>StateFlow</code> is commonly used to hold and emit the UI state in the MVVM pattern often used in Android. For example, one might have a <code>ViewModel</code> that exposes a <code>StateFlow</code> of a data class to describe the view state. The view state could be described as a data class.</p>
<pre>
class MyViewModel : ViewModel() { data class ViewState(
val showLoading: Boolean = false,
val title: String = "Default Title",
val doneButtonEnabled: Boolean = true
) private val _viewState = MutableStateFlow<ViewState>(ViewState())
val viewState = _viewState.asStateFlow()}</pre>
<p>An activity or fragment may consume said flow and use the values emitted by it to change the UI state. Note that I’m not going to cover how to safely observe flows within the android lifecycle, that isn’t the focus of this article. <a href="https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda" rel="noopener">Manuel Vivo’s article</a> covers that well. (Shameless plug for <a href="https://proandroiddev.com/android-singleliveevent-redux-with-kotlin-flow-b755c70bb055" rel="noopener ugc nofollow" target="_blank">my own</a>, older and slightly out of date article covering a similar topic too.)</p>
<pre>
class MyFragment : Fragment(R.layout.<em>fragment</em>) {
... // Note I'm ignoring details about where the flows are observed
// There are some good reasons to be careful how your flows
// are observed here, but since that isn't the focus of the
// article I'm omitting them.
viewModel.viewState
.onEach {
if (it.showLoading) {
// update the UI to show loading
}
// and so on with the other parts of the view state
}
.launchIn... ...}</pre>
<p>Using a data class to describe UI state is a very convenient mechanism when coupled with StateFlow. You can observe the flow repeatedly and always get the most current UI state. The observed value is a trivial data class that can be decomposed into its component properties very simply. (I would note that using a sealed class as your UI state can exhibit the same behaviour described in this article, it’s just more complex to demonstrate.)</p>
<p>Furthermore, updating the state is easy with data classes. Data classes have a convenient <code>copy</code> function that allows you to update one or more properties of the data class while preserving the remaining values. So you can very trivially update the UI state:</p>
<pre>
// Update just one property, leave the rest as is without
// caring what the values are.
val newUIState = _viewState.value.copy(doneButtonEnabled = true)
_viewState.value = newUIState // emit the new UI state</pre>
<p>Or better yet just make it all a one liner:</p>
<pre>
_viewState.value = _viewState.value.copy(doneButtonEnabled = true)</pre>
<p>That looks very simple and very straightforward. Whatever other properties are set in the UI state only the <code>doneButtonEnabled</code> property is updated; the remaining properties are preserved.</p>
<p><a href="https://medium.com/geekculture/atomic-updates-with-mutablestateflow-dc0331724405">Click Here</a></p>