Atomic Updates on MutableStateFlow

<p>I&rsquo;m going to describe this problem in the context of Android, since that&rsquo;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>&nbsp;is commonly used to hold and emit the UI state in the MVVM pattern often used in Android. For example, one might have a&nbsp;<code>ViewModel</code>&nbsp;that exposes a&nbsp;<code>StateFlow</code>&nbsp;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 = &quot;Default Title&quot;, val doneButtonEnabled: Boolean = true ) private val _viewState = MutableStateFlow&lt;ViewState&gt;(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&rsquo;m not going to cover how to safely observe flows within the android lifecycle, that isn&rsquo;t the focus of this article.&nbsp;<a href="https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda" rel="noopener">Manuel Vivo&rsquo;s article</a>&nbsp;covers that well. (Shameless plug for&nbsp;<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&#39;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&#39;t the focus of the // article I&#39;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&rsquo;s just more complex to demonstrate.)</p> <p>Furthermore, updating the state is easy with data classes. Data classes have a convenient&nbsp;<code>copy</code>&nbsp;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&nbsp;<code>doneButtonEnabled</code>&nbsp;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>