StateFlow i SharedFlow

StateFlowSharedFlow to interfejsy API Flow, które umożliwiają optymalne emitowanie aktualizacji stanu i wartości do wielu odbiorców.

StateFlow

StateFlow to obserwowalny przepływ, który przechowuje stan i emituje do kolektorów aktualny i nowy stan. Wartość bieżącego stanu można też odczytać za pomocą właściwości value. Aby zaktualizować stan i przesłać go do przepływu, przypisz nową wartość do właściwości value klasy MutableStateFlow.

W Androidzie StateFlow doskonale sprawdza się w przypadku klas, które muszą utrzymywać obserwowalny stan modyfikowalny.

Zgodnie z przykładami z artykułu Przepływy w Kotlinie można udostępnić StateFlowLatestNewsViewModel, aby View mogła nasłuchiwać aktualizacji stanu interfejsu i sprawić, że stan ekranu przetrwa zmiany konfiguracji.

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update UI with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>) : LatestNewsUiState()
    data class Error(val exception: Throwable) : LatestNewsUiState()
}

Klasa odpowiedzialna za aktualizowanie MutableStateFlow to producent, a wszystkie klasy pobierające dane z StateFlow to konsumenci. W przeciwieństwie do zimnego przepływu utworzonego za pomocą narzędzia flow przepływ StateFlow jest gorący: zbieranie danych z przepływu nie wywołuje żadnego kodu producenta. Obiekt StateFlow jest zawsze aktywny i znajduje się w pamięci. Może zostać usunięty przez mechanizm odśmiecania tylko wtedy, gdy nie ma do niego innych odwołań z poziomu głównego elementu odśmiecania.

Gdy nowy odbiorca zaczyna zbierać dane ze strumienia, otrzymuje ostatni stan w strumieniu i wszystkie kolejne stany. Takie zachowanie można zaobserwować w innych klasach, np. LiveData.

View nasłuchuje StateFlow tak jak w przypadku każdego innego przepływu:

class LatestNewsActivity : ComponentActivity() {
    private val latestNewsViewModel: LatestNewsViewModel = TODO() // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

Aby przekształcić dowolny przepływ w StateFlow, użyj operatora pośredniego stateIn.

StateFlow, Flow i LiveData

StateFlowLiveData są podobne. Obie klasy są klasami posiadaczy danych, które można obserwować, i obie są używane w architekturze aplikacji w podobny sposób.

Pamiętaj jednak, że symbole StateFlowLiveData działają inaczej:

  • StateFlow wymaga przekazania stanu początkowego do konstruktora, a LiveData nie.
  • LiveData.observe() automatycznie wyrejestrowuje konsumenta, gdy widok przechodzi w stan STOPPED, natomiast zbieranie danych w przypadku stanu StateFlow lub dowolnego innego procesu nie zatrzymuje się automatycznie. Aby uzyskać ten sam efekt, musisz zebrać przepływ z bloku Lifecycle.repeatOnLifecycle.

Podgrzewanie zimnych strumieni za pomocą shareIn

StateFlow to gorący przepływ – pozostaje w pamięci tak długo, jak długo jest zbierany lub dopóki istnieją do niego inne odwołania z poziomu głównego elementu odśmiecania pamięci. Możesz przekształcić zimne przepływy w gorące, używając operatora shareIn.

Na przykładzie callbackFlow utworzonego w przepływach Kotlin zamiast tworzyć nowy przepływ dla każdego kolektora możesz udostępniać dane pobrane z Firestore między kolektorami za pomocą shareIn. Musisz przekazać te informacje:

  • CoroutineScope, która służy do udostępniania przepływu. Ten zakres powinien być aktywny dłużej niż jakikolwiek konsument, aby zachować aktywność współdzielonego przepływu tak długo, jak to konieczne.
  • Liczba elementów do odtworzenia dla każdego nowego kolektora.
  • Zasady dotyczące zachowania na początku.

class NewsRemoteDataSource(
    private val externalScope: CoroutineScope
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

W tym przykładzie przepływ latestNews odtwarza ostatni wyemitowany element do nowego kolektora i pozostaje aktywny tak długo, jak długo externalScope jest aktywny i istnieją aktywne kolektory. Zasada SharingStarted.WhileSubscribed() start utrzymuje aktywnego producenta nadrzędnego, dopóki są aktywni subskrybenci. Dostępne są inne zasady uruchamiania, np. SharingStarted.Eagerly natychmiastowe uruchomienie producenta lub SharingStarted.Lazily rozpoczęcie udostępniania po pojawieniu się pierwszego subskrybenta i utrzymywanie przepływu w stanie aktywnym na zawsze.

SharedFlow

Funkcja shareIn zwraca SharedFlow, czyli gorący strumień, który emituje wartości do wszystkich odbiorców, którzy z niego pobierają dane. SharedFlow to wysoce konfigurowalna generalizacja StateFlow.

Możesz utworzyć SharedFlow bez użycia shareIn. Możesz na przykład użyć SharedFlow, aby wysyłać sygnały do pozostałej części aplikacji, dzięki czemu wszystkie treści będą odświeżane okresowo w tym samym czasie. Oprócz pobierania najnowszych wiadomości możesz też odświeżać sekcję informacji o użytkowniku, wyświetlając jego ulubione tematy. W tym fragmencie kodu TickHandler udostępnia SharedFlow, aby inne klasy wiedziały, kiedy odświeżyć jego zawartość. Podobnie jak w przypadku StateFlow, użyj właściwości pomocniczej typu MutableSharedFlow w klasie, aby wysyłać elementy do przepływu:

// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Unit> = _tickFlow

    init {
        externalScope.launch {
            while (true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    // ...
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { /* ... */ }
    // ...
}

Działanie przycisku SharedFlow możesz dostosować w ten sposób:

  • replay umożliwia ponowne wysłanie do nowych subskrybentów określonej liczby wcześniej wyemitowanych wartości.
  • onBufferOverflow pozwala określić zasady dotyczące sytuacji, w której bufor jest pełny elementów do wysłania. Wartością domyślną jest BufferOverflow.SUSPEND, co powoduje zawieszenie wywołującego. Inne opcje to DROP_LATEST lub DROP_OLDEST.

MutableSharedFlow ma też właściwość subscriptionCount, która zawiera liczbę aktywnych kolektorów, dzięki czemu możesz odpowiednio optymalizować logikę biznesową. MutableSharedFlow zawiera też resetReplayCache funkcję, jeśli nie chcesz ponownie odtwarzać najnowszych informacji wysłanych do przepływu.

Dodatkowe zasoby dotyczące przepływu