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
StateFlow
i LiveData
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 naSTOPPED
. Zbieranie danych za pomocąStateFlow
lub innego procesu nie przestaje działać automatycznie. Aby uzyskać takie samo działanie, musisz zebrać przepływ z blokuLifecycle.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 toBufferOverflow.SUSPEND
, która powoduje zawieszenie wywołującego. Inne opcje toDROP_LATEST
lubDROP_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
- Kotlin Flow na Androidzie
- Testowanie procesów Kotlina na Androidzie
- Co warto wiedzieć o operatorachshareIn i stateIn Flow
- Migracja z LiveData do Kotlin Flow
- Dodatkowe materiały na temat współrzędnych i przepływu pracy Kotlin