· 2 min czytania
Jak poprawnie aktualizować stan StateFlow w Kotlinie?
StateFlow to częsty wybór do przechowywania stanu aplikacji i widoków w Androidzie. Czy wiesz jak korzystać z niego poprawnie?
Ostatnio została wydana nowa wersja biblioteki Kotlin Coroutines z kilkoma nowymi extensionami, które mają pomóc Ci w aktualizacjach StateFlow. Wszystko zaczęło się od tego issue na GitHubie:
https://github.com/Kotlin/kotlinx.coroutines/issues/2720
Przyjrzyjmy się temu bliżej 👇
StateFlow
StateFlow jest głównie używany do obsługi stanu i jego aktualizacji. W przypadku Androida najczęściej spotykany jest w ViewModel’ach, gdzie wystawia na zewnątrz stan, który zostaje skonsumowany w widokach.
Zobaczmy przykład:
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
)
Jak aktualizować stan?
Można to zrobić w bardzo łatwy sposób, ponieważ używamy data classes:
_uiState.value = _uiState.value.copy(title = "Something")
Proste, prawda? Jednak nie do końca poprawne…
W czym problem?
Chociaż kod jest bardzo prosty, istnieje coś, o czym nigdy nie mozesz zapomnieć: wielowątkowość.
Jeśli między momentem zakończenia działania funkcji copy a momentem emisji nowej wartości przez StateFlow inny wątek spróbuje zaktualizować StateFlow (używając copy i aktualizując jedną z właściwości, której bieżąca kopia nie modyfikuje) możemy zakończyć z wynikami, których zupełnie się nie spodziewaliśmy.
Bądź na bieżąco z Androidem 📱
Dołącz do newsletter'a i otrzymuj więcej takich treści regularnie na swoją skrzynkę mailową 📬
Rozwiązanie
Jak rozwiązać ten problem? Poprzez użycie nowych extensionów z Kotlin Coroutines dla MutableStateFlow.
Mowa tu o metodach:
- update
- updateAndGet
- getAndUpdate
Każda z nich przyjmuje lambdę jako parametr, która określa w jaki sposób ma zmienić się stan, który zostanie wyemitowany.
Spójrzmy na chwilę, co znajduje się wewnątrz metody update:
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
Funkcja compareAndSet
używana jest aby określić, czy wartość uległa zmianie — na przykład przez inny wątek. Jeśli compareAndSet
zwróci false, pętla while będzie działać w kółko, dopóki nie będzie możliwe zaktualizowanie stanu.
_uiState.update { it.copy(title = "Something") }
No! Teraz lepiej :)