StateFlow 和 SharedFlow 是資料流 API,能讓資料流以最佳方式發出狀態更新,並向多個取用端發出值。
StateFlow
StateFlow 是狀態容器的可觀測資料流,可對相關的收集器發出目前和新的狀態更新。目前狀態值也可以透過其 value 屬性讀取。如要更新狀態並傳送至資料流,請將新的值指派給 MutableStateFlow 類別的 value 屬性。
在 Android 中,StateFlow 非常適合需要維持可觀測、可變動狀態的類別。
按照 Kotlin 資料流的範例,StateFlow 可以從 LatestNewsViewModel 公開,以便 View 監聽 UI 狀態更新,並且使螢幕狀態在設定變更後自行繼續留存。
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() }
負責更新 MutableStateFlow 的類別為生產端,而從 StateFlow 收集的所有類別為取用端。與使用 flow 建構工具建構的「冷」資料流不同,StateFlow 是「熱」資料流:從資料流收集,不會觸發任何生產端程式碼。StateFlow 會一直處於啟用中狀態,且會留在記憶體中,但僅當垃圾收集的根層級沒有其他對該資料流的參照時,才可用於垃圾收集。
當新的取用端開始從資料流收集資訊時,它就會收到串流中的最後一個狀態和任何後續狀態。您可以在其他可觀測的類別中發現這種行為,例如 LiveData。
就像任何其他資料流一樣,View 會監聽 StateFlow:
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) } } } } } }
如要將任何資料流轉換為 StateFlow,請使用 stateIn 中繼運算子。
StateFlow、資料流和 LiveData
StateFlow 和 LiveData 有相似之處。兩者都是可觀測的資料容器類別,且在應用程式架構中使用時,都會遵循類似的模式。
但請注意,StateFlow 和 LiveData 的執行方式不同:
StateFlow需要將初始狀態傳遞到建構函式,LiveData卻不需要。- 當檢視畫面進入
STOPPED狀態時,LiveData.observe()會自動取消註冊取用端,而從StateFlow或任何其他資料流收集的作業不會自動停止。如要執行相同的行為,您需要從Lifecycle.repeatOnLifecycle區塊收集資料流。
使用 shareIn 讓冷資料流變熱
StateFlow 是「熱」資料流;只要收集該資料流,或垃圾收集的根層級存有對該資料流的任何其他參照,它就會留存在記憶體中。您可以使用 shareIn 運算子,將冷資料流轉換為熱資料流。
以 Kotlin 資料流中建立的 callbackFlow 為例,只要使用 shareIn,即可在收集器之間共用從 Firestore 擷取的資料,而不必讓每個收集器建立新資料流。您需要傳入以下項目:
- 用來共用資料流的
CoroutineScope。這個範圍應比任何取用端留存更久,才可視需要使共用的資料流繼續留存。 - 向每個新收集器重播的項目數量。
- 啟動行為政策。
class NewsRemoteDataSource( private val externalScope: CoroutineScope ) { val latestNews: Flow<List<ArticleHeadline>> = flow { // ... }.shareIn( externalScope, replay = 1, started = SharingStarted.WhileSubscribed() ) }
在這個範例中,latestNews 資料流會向新收集器重送最後一個發出的項目,且只要 externalScope 繼續留存,並有啟用中的收集器,就會保持啟用中狀態。當有活躍的訂閱者時,SharingStarted.WhileSubscribed() 啟動政策會使上游生產端保持啟用中狀態。您也可以使用其他啟動政策,例如使用 SharingStarted.Eagerly 即可立即啟動生產端;如果使用 SharingStarted.Lazily,即可在第一個訂閱者出現後開始共用資料,並使資料流永久保持啟用狀態。
SharedFlow
shareIn 函式會傳回 SharedFlow。後者是熱資料流,會向從其收集資料的所有取用端發送值。SharedFlow 是 StateFlow 的可高度設定一般化功能。
即使不使用 shareIn,您仍可建立 SharedFlow。例如,您可以使用 SharedFlow 將滴答傳送至應用程式的其餘部分,這樣所有內容就會同時定期重新整理。除了擷取最新消息外,建議您一併重新整理使用者資訊部分,附上最愛的主題集合。在以下程式碼片段中,TickHandler 會公開 SharedFlow,方便其他類別知道重新整理內容的時間。和 StateFlow 一樣,請使用類別中 MutableSharedFlow 類型的支援屬性將項目傳送至資料流:
// 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() { /* ... */ } // ... }
您可以透過下列方式自訂 SharedFlow 行為:
replay可讓您重新傳送許多先前為訂閱者發出的值。onBufferOverflow可指定用於規定緩衝區何時充滿要傳送的項目的政策。預設值為BufferOverflow.SUSPEND,可使呼叫端暫停。其他選項為DROP_LATEST或DROP_OLDEST。
MutableSharedFlow 也具有 subscriptionCount 屬性,其中會指出有多少個啟用中的收集器,讓您可視情況將商業邏輯最佳化。如果不想重播傳送至資料流的最新資訊,也可使用 MutableSharedFlow 的 resetReplayCache 函式。
其他資料流資源
- Android 上的 Kotlin 資料流
- 在 Android 上測試 Kotlin 資料流
- 資料流的 shareIn 和 StateIn 運算子必學知識
- 從 LiveData 遷移至 Kotlin 資料流
- Kotlin 協同程式和資料流的其他資源