StateFlow i SharedFlow

StateFlow i SharedFlow to interfejsy API przepływu, które umożliwiają przepływom w celu optymalnego wysyłania aktualizacji stanu i wysyłania wartości wielu klientom.

StateFlow

StateFlow to obserwowalny przepływ danych, który emituje bieżące i nowe aktualizacje stanu do swoich kolektorów. Wartość bieżącego stanu można też odczytać za pomocą jej 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 nadaje się do klas, które muszą utrzymywać obserwowalny stan zmienny.

Zgodnie z przykładami w przepływach kodu Kotlin możesz udostępnić StateFlow z poziomu LatestNewsViewModel, aby View mogła nasłuchiwać zmian stanu interfejsu użytkownika i utrzymywać stan ekranu w stanie niezmienionym po zmianach 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 View 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 aktualizację obiektu MutableStateFlow jest producentem, a wszystkie klasy pobierane z obiektu StateFlow to konsumenci. W odróżnieniu od zimnego przepływu utworzonego za pomocą kreatora flow, StateFlow jest gorący: zbieranie danych z tego przepływu nie powoduje wywołania żadnego kodu producenta. Obiekt StateFlow jest zawsze aktywny i znajduje się w pamięci. Może zostać przekazany do zbiorczego usuwania tylko wtedy, gdy nie ma innych odwołań do niego z korzenia zbiorczego usuwania.

Gdy nowy konsument rozpocznie zbieranie danych z przepływu, otrzyma ostatni stan w strumieniach i wszystkie kolejne stany. Takie zachowanie możesz zaobserwować w innych klasach, takich jak LiveData.

View wykrywa StateFlow tak samo jak w przypadku każdego innego przepływu:

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // 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 przekonwertować dowolny przepływ na StateFlow, użyj operatora pośredniego stateIn.

StateFlow, Flow i LiveData

StateFlowLiveData są do siebie podobne. Obie są obserwowalnymi klasami uchwytów danych i w obu przypadkach w architekturze aplikacji stosuje się podobny wzór.

Pamiętaj jednak, że StateFlow i LiveData działają inaczej:

  • Funkcja StateFlow wymaga przekazania do konstruktora stanu początkowego, w przeciwieństwie do niej – LiveData.
  • Funkcja LiveData.observe() automatycznie wyrejestrowuje konsumenta, gdy widok zmieni stan na STOPPED. Zbieranie danych za pomocą StateFlow lub innego procesu nie przestaje działać automatycznie. Aby uzyskać takie samo działanie, musisz zebrać przepływ z bloku Lifecycle.repeatOnLifecycle.

Przekształcanie zimnych procesów w ciepłe za pomocą shareIn

StateFlow to gorący przepływ: pozostaje w pamięci tak długo, jak długo jest zbierany lub gdy istnieją inne odwołania do niego z korzenia zbioru. Możesz włączyć aktywne przepływy za pomocą operatora shareIn.

Korzystając na przykład z elementu callbackFlow utworzonego w strumieniach kodu Kotlin, zamiast tworzenia nowego strumienia przez każdego kolektora, możesz udostępniać dane pobrane z Firestore między kolektorami za pomocą elementu shareIn. Musisz podać te informacje:

  • CoroutineScope służący do udostępniania przepływu. Aby wspólny przepływ działał tak długo, jak to konieczne, ten zakres powinien być dłuższy niż jakikolwiek konsument.
  • Liczba elementów do ponownego 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 pozostanie aktywny, dopóki externalScope będzie aktywny i będą aktywni kolektorzy. Zasada startowa SharingStarted.WhileSubscribed() utrzymuje aktywność producenta nadrzędnego, gdy istnieją aktywni subskrybenci. Dostępne są też inne zasady uruchamiania, np. SharingStarted.Eagerly, aby natychmiast rozpocząć udostępnianie producenta, lub SharingStarted.Lazily, aby rozpocząć udostępnianie po pojawieniu się pierwszego subskrybenta, i zapewnić nieprzerwaną aktywność.

SharedFlow

Funkcja shareIn zwraca SharedFlow, czyli przepływ gorący, który przesyła wartości do wszystkich użytkowników, którzy pobierają z niego dane. SharedFlow to StateFlow z dużą możliwością konfiguracji.

Możesz utworzyć SharedFlow bez shareIn. Możesz na przykład użyć elementu SharedFlow, aby wysyłać znaczniki do pozostałej części aplikacji, aby treści były okresowo odświeżane w tym samym czasie. Oprócz pobierania najnowszych wiadomości możesz też odświeżyć sekcję informacji o użytkowniku, dodając do niej kolekcję ulubionych tematów. W tym fragmencie kodu klasa TickHandler udostępnia klasę SharedFlow, aby inne klasy wiedziały, kiedy odświeżyć jej zawartość. Podobnie jak w przypadku właściwości StateFlow, użyj w klasie właściwości pomocniczej typu MutableSharedFlow, aby przesył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<Event<String>> = _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 SharedFlow możesz dostosować na te sposoby:

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

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

Dodatkowe zasoby przepływu