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:
StateFlowwymaga przekazania stanu początkowego do konstruktora, aLiveDatanie.LiveData.observe()automatycznie wyrejestrowuje odbiorcę, gdy widok przechodzi do stanuSTOPPED, natomiast zbieranie danych zStateFlowlub innego przepływu nie zatrzymuje się automatycznie. Aby uzyskać to samo zachowanie, musisz zbierać dane z przepływu z blokuLifecycle.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:
replayumożliwia ponowne wysłanie określonej liczby wcześniej wyemitowanych wartości do nowych subskrybentów.onBufferOverflowumożliwia określenie zasady, która ma być stosowana, gdy bufor jest pełny elementów do wysłania. Wartość domyślna toBufferOverflow.SUSPEND, która powoduje zawieszenie wywołującego. Inne opcje toDROP_LATESTlubDROP_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
- Przepływy Kotlin w Androidzie
- Testowanie przepływów Kotlin w Androidzie
- Co warto wiedzieć o operatorach shareIn i stateIn w Flow
- Migracja z LiveData do Kotlin Flow
- Dodatkowe materiały dotyczące współprogramów i przepływu Kotlin