StateFlow i SharedFlow

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

StateFlow

StateFlow to obserwowalny przepływ na terenie stanu, który przekazuje do kolektorów aktualne i nowe aktualizacje stanu. Bieżąca wartość stanu można też odczytać za pomocą jej właściwości value. Aby zaktualizować stan i wysłać go do przepływu, przypisz nową wartość do właściwości value klasy MutableStateFlow.

Na Androidzie StateFlow bardzo dobrze sprawdza się w klasach, które muszą zachowywać obserwowalny stan zmienny.

Zgodnie z przykładami z przepływów w Kotlina element StateFlow może być widoczny z poziomu LatestNewsViewModel, aby usługa View mogła nasłuchiwać aktualizacji stanu interfejsu i umożliwić przetrwanie zmian konfiguracji przez stan ekranu.

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 aktualizowanie elementu MutableStateFlow jest producentem, a wszystkie klasy pobierające z klasy StateFlow są konsumentami. W przeciwieństwie do przepływu zimnego utworzonego za pomocą konstruktora flow element StateFlow jest gorący: pobieranie z przepływu nie uruchamia żadnego kodu producenta. Element StateFlow jest zawsze aktywny i znajduje się w pamięci. kwalifikuje się do czyszczenia pamięci tylko wtedy, gdy nie ma do niego żadnych innych odwołań z poziomu głównego kosza pamięci.

Gdy nowy klient rozpocznie zbieranie danych z przepływu, otrzymuje ostatni stan w strumieniu i wszystkie kolejne. Możesz je znaleźć w innych klasach możliwych do obserwowania, takich jak LiveData.

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

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

StateFlow i LiveData są podobne. Obie są obserwowanymi klasami posiadaczy danych i korzystają z podobnego wzorca w architekturze aplikacji.

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

  • StateFlow wymaga przekazania konstruktorowi stanu początkowego, natomiast LiveData tego nie wymaga.
  • LiveData.observe() automatycznie wyrejestruje konsumenta, gdy widok danych przejdzie w stan STOPPED. Pobieranie z StateFlow lub innego procesu nie zatrzymuje automatycznego zbierania danych. Aby osiągnąć to samo zachowanie, musisz zbierać przepływ z bloku Lifecycle.repeatOnLifecycle.

Podgrzewanie przepływu zimnego za pomocą funkcji shareIn

StateFlow to przepływ gorący – pozostaje w pamięci, dopóki przepływ zostanie zebrany lub gdy istnieją do niego inne odwołania z katalogu głównego kosza pamięci masowej. Przepływ „na zimno” możesz przełączyć na ciepło, używając operatora shareIn.

Korzystając z przykładu obiektu callbackFlow utworzonego w przepływach Kotlin, zamiast tworzyć nowy przepływ przez każdy kolektor, możesz udostępniać dane pobrane z Firestore między kolektorami za pomocą metody shareIn. Musisz spełnić te wymagania:

  • CoroutineScope, który służy do udostępniania procesu. Aby udostępniony przepływ mógł działać tak długo, jak to konieczne, ten zakres powinien być aktywny dłużej niż jakikolwiek inny konsument.
  • Liczba elementów do odtworzenia dla każdego nowego kolektora.
  • Zasada działania początkowego.
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 w nowym kolektorze i pozosta aktywny, dopóki element externalScope jest aktywny i istnieją kolektory. Zasada uruchamiania SharingStarted.WhileSubscribed() pozostaje aktywna, gdy istnieją aktywni subskrybenci. Dostępne są inne zasady początkowe, na przykład SharingStarted.Eagerly uruchamiające producenta natychmiast lub SharingStarted.Lazily, które powoduje rozpoczęcie udostępniania po pojawieniu się pierwszego subskrybenta i utrzymanie przepływu aktywności na zawsze.

SharedFlow

Funkcja shareIn zwraca SharedFlow, czyli przepływ gorący, który przekazuje wartości wszystkim konsumentom, którzy z niej korzystają. SharedFlow to konfigurowalne uogólnienie właściwości StateFlow.

Możesz utworzyć SharedFlow bez korzystania z shareIn. Możesz na przykład użyć polecenia SharedFlow, aby wysyłać znaczniki do reszty aplikacji, tak aby cała zawartość była okresowo odświeżana w tym samym czasie. Oprócz pobierania najnowszych wiadomości możesz też odświeżyć sekcję z informacjami o użytkownikach, dodając kolekcję ulubionych tematów. W tym fragmencie kodu TickHandler udostępnia obiekt SharedFlow, dzięki czemu inne klasy wiedzą, kiedy odświeżyć jego zawartość. Podobnie jak w przypadku StateFlow, użyj w klasie właściwości zapasowej typu MutableSharedFlow, 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<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 szeregu wartości wcześniej wysłanych dla nowych subskrybentów.
  • onBufferOverflow pozwala określić zasadę, która powoduje, że bufor jest pełen elementów do wysłania. Wartość domyślna to BufferOverflow.SUSPEND, co powoduje, że element wywołujący zostaje zawieszony. 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 zoptymalizować logikę biznesową. MutableSharedFlow zawiera też funkcję resetReplayCache, jeśli nie chcesz odtwarzać ponownie najnowszych informacji przesłanych do procesu.

Dodatkowe materiały na temat przepływu