· 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?

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 :)

Udostępnij:
Powrót do Bloga