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, natomiastLiveData
tego nie wymaga.LiveData.observe()
automatycznie wyrejestruje konsumenta, gdy widok danych przejdzie w stanSTOPPED
. Pobieranie zStateFlow
lub innego procesu nie zatrzymuje automatycznego zbierania danych. Aby osiągnąć to samo zachowanie, musisz zbierać przepływ z blokuLifecycle.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 toBufferOverflow.SUSPEND
, co powoduje, że element wywołujący zostaje zawieszony. Inne opcje toDROP_LATEST
lubDROP_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
- Kotlin na Androidzie
- Testowanie przepływów aplikacji Kotlin na Androidzie
- Co warto wiedzieć o operatorach ShareIn i stateIn w Dataflow
- Migracja z LiveData do Kotlin Flow
- Dodatkowe materiały dotyczące współprogramów i przepływu pracy Kotlin