· 2 min read
Make sure to update your StateFlow safely in Kotlin!
StateFlow is a common choice for storing app state in Android, e.g., view state. But do you know how to use it correctly?
Recently a new version of Kotlin Coroutines library was released with a few new extensions functions to help you with StateFlow updates. It all started with this issue:
https://github.com/Kotlin/kotlinx.coroutines/issues/2720
Let’s see what it’s all about… 👇
StateFlow
StateFlow is mostly used for handling state and state updates. It can be used for example in Android’s ViewModel to expose state to your views.
Let’s see an example:
class ExampleViewModel(
private val initialState: ExampleViewState
) : ViewModel() {
private val _uiState: MutableStateFlow<ExampleViewState> = MutableStateFlow(initialState)
val uiState = _uiState.asStateFlow()
}
data class ExampleViewState(
val title: String = "",
val description: String = ""
// other state variables
)
How to update the state?
Well, it’s really easy, since we use a data class. We can just use copy like this:
_uiState.value = _uiState.value.copy(title = "Something")
Simple, right? But you have to be careful…
Where’s the catch?
So what’s the problem? While the code is very simple, there is something you have to be aware of - concurrency.
If between the time copy function completes and the StateFlow’s new value is emitted another thread tries to update the StateFlow — by using copy and updating one of the properties that the current copy isn’t modifying —we could end up with results we were not expecting.
Stay updated with Android 📱
Join the newsletter and receive more content like this regularly in your inbox 📬
The solution
How do we solve this problem? By using fresh good stuff from Kotlin Coroutines for MutableStateFlow.
I’m talking about are these three methods below:
- update
- updateAndGet
- getAndUpdate
All of these take a function parameter that returns the new state that will be emitted.
Let’s see what’s inside the update method for a moment:
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
As we can see, a function is passed as a param and it’s applied to the current StateFlow’s value. Then compareAndSet function is used to determine if the value has changed — for example by another thread. If compareAndSet returns false then the while loop will be running until it’s possible to update the state.
So how to use it properly?
_uiState.update { it.copy(title = "Something") }
That’s it! This ensures us that the state can be changed safely 👍