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 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()
}
負責更新 MutableStateFlow
的類別為生產端,而從 StateFlow
收集的所有類別為取用端。與使用 flow
建構工具建構的「冷」資料流不同,StateFlow
是「熱」資料流:從資料流收集,不會觸發任何生產端程式碼。StateFlow
會一直處於啟用中狀態,且會留在記憶體中,但僅當垃圾收集的根層級沒有其他對該資料流的參照時,才可用於垃圾收集。
當新的取用端開始從資料流收集資訊時,它就會收到串流中的最後一個狀態和任何後續狀態。您可以在其他可觀測的類別中發現這種行為,例如 LiveData
。
就像任何其他資料流一樣,View
會監聽 StateFlow
:
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)
}
}
}
}
}
}
如要將任何資料流轉換為 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<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() { ... }
...
}
您可以透過下列方式自訂 SharedFlow
行為:
replay
可讓您重新傳送許多先前為訂閱者發出的值。onBufferOverflow
可指定用於規定緩衝區何時充滿要傳送的項目的政策。預設值為BufferOverflow.SUSPEND
,可使呼叫端暫停。其他選項為DROP_LATEST
或DROP_OLDEST
。
MutableSharedFlow
也具有 subscriptionCount
屬性,其中會指出有多少個啟用中的收集器,讓您可視情況將商業邏輯最佳化。如果不想重播傳送至資料流的最新資訊,也可使用 MutableSharedFlow
的 resetReplayCache
函式。
其他資料流資源
- Android 上的 Kotlin 資料流
- 在 Android 上測試 Kotlin 資料流
- 資料流的 shareIn 和 StateIn 運算子必學知識
- 從 LiveData 遷移至 Kotlin 資料流
- Kotlin 協同程式和資料流的其他資源