StateFlow i SharedFlow

StateFlow i SharedFlow 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 przechowujący stan, który emituje bieżące i nowe aktualizacje stanu do swoich kolektorów. Bieżącą wartość stanu można też odczytać za pomocą 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.

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

Zgodnie z przykładami z Kotlin Flows można udostępnić StateFlow z LatestNewsViewModel, aby View mogła nasłuchiwać aktualizacji stanu interfejsu i w ten sposób zachować stan ekranu 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 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 jest producentem, a wszystkie klasy zbierające dane z StateFlow są odbiorcami. W przeciwieństwie do zimnego przepływu utworzonego za pomocą konstruktora flow przepływ StateFlow jest gorący: zbieranie danych z przepływu nie wywołuje żadnego kodu producenta. StateFlow jest zawsze aktywny i znajduje się w pamięci. Może zostać usunięty przez moduł odśmiecania pamięci tylko wtedy, gdy nie ma do niego żadnych odwołań z poziomu głównego modułu odśmiecania pamięci.

Gdy nowy odbiorca zacznie zbierać dane z przepływu, otrzyma ostatni stan w strumieniu i wszystkie kolejne stany. Takie zachowanie można zaobserwować w innych klasach obserwowalnych, np. LiveData.

View nasłuchuje StateFlow tak samo jak 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 przekonwertować dowolny przepływ na StateFlow, użyj stateIn operatora pośredniego.

StateFlow, Flow i LiveData

StateFlow i LiveData mają podobieństwa. Obie są klasami przechowującymi dane, które można obserwować, i obie są używane w architekturze aplikacji w podobny sposób.

Pamiętaj jednak, że StateFlow i LiveData zachowują się inaczej:

  • StateFlow wymaga przekazania stanu początkowego do konstruktora, a LiveData nie.
  • LiveData.observe() automatycznie wyrejestrowuje odbiorcę, gdy widok przechodzi do stanu STOPPED, natomiast zbieranie danych z StateFlow lub innego przepływu nie zatrzymuje się automatycznie. Aby uzyskać to samo zachowanie, musisz zbierać dane z przepływu z bloku Lifecycle.repeatOnLifecycle.

Przekształcanie zimnych przepływów w gorące 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 modułu odśmiecania pamięci. Zimne przepływy możesz przekształcić w gorące za pomocą shareIn operatora.

Na przykładzie callbackFlow utworzonego w Kotlin Flows jako przykład, 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óry służy do udostępniania przepływu. Ten zakres powinien być dłuższy niż jakikolwiek odbiorca, aby udostępniony przepływ był aktywny tak długo, jak to konieczne.
  • Liczba elementów do odtworzenia dla każdego nowego kolektora.
  • Zasada dotycząca zachowania 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 do nowego kolektora i pozostaje aktywny tak długo, jak długo aktywny jest externalScope i istnieją aktywni kolektorzy. Zasada początkowa SharingStarted.WhileSubscribed() utrzymuje aktywność producenta nadrzędnego, dopóki są aktywni subskrybenci. Dostępne są też inne zasady początkowe, np. SharingStarted.Eagerly, aby natychmiast uruchomić producenta, lub SharingStarted.Lazily, aby rozpocząć udostępnianie po pojawieniu się pierwszego subskrybenta i utrzymywać przepływ aktywny na zawsze.

SharedFlow

Funkcja shareIn zwraca SharedFlow, czyli gorący przepływ, który emituje wartości do wszystkich odbiorców, którzy z niego zbierają dane. SharedFlow to wysoce konfigurowalne uogólnienie StateFlow.

SharedFlow możesz utworzyć bez użycia shareIn. Możesz na przykład użyć SharedFlow, aby wysyłać sygnały do reszty aplikacji, tak aby cała zawartość odświeżała się okresowo w tym samym czasie. Oprócz pobierania najnowszych wiadomości możesz też odświeżać sekcję informacji o użytkowniku za pomocą kolekcji ulubionych tematów. W tym fragmencie kodu TickHandler udostępnia SharedFlow, aby inne klasy wiedziały, kiedy odświeżyć jego zawartość. Podobnie jak w przypadku StateFlow, aby wysyłać elementy do przepływu, użyj właściwości zapasowej typu MutableSharedFlow w klasie:

// 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() { /* ... */ }
    // ...
}

Zachowanie 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 umożliwia określenie zasady, która ma być stosowana, 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.

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ć najnowszych informacji wysłanych do przepływu.

Dodatkowe materiały dotyczące przepływu