StateFlow 和 SharedFlow

StateFlowSharedFlow資料流 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

StateFlowLiveData 有相似之處。兩者都是可觀測的資料容器類別,且在應用程式架構中使用時,都會遵循類似的模式。

但請注意,StateFlowLiveData 的執行方式不同:

  • 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。後者是熱資料流,會向從其收集資料的所有取用端發送值。SharedFlowStateFlow 的可高度設定一般化功能。

即使不使用 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_LATESTDROP_OLDEST

MutableSharedFlow 也具有 subscriptionCount 屬性,其中會指出有多少個啟用中的收集器,讓您可視情況將商業邏輯最佳化。如果不想重播傳送至資料流的最新資訊,也可使用 MutableSharedFlowresetReplayCache 函式。

其他資料流資源