Android 可觀測數據流(StateFlow 與 SharedFlow)

上一章介紹的 Flow 是「冷流」(Cold Stream),只有在被收集時才會執行。但在 Android 開發中,我們常需要「熱流」(Hot Stream):無論是否有訂閱者,資料流都可能持續活躍,或者保留最新的狀態給新的訂閱者。

這就是 StateFlowSharedFlow 的用途。它們是用來取代 LiveData 的現代化方案。

StateFlow:狀態持有者

StateFlow 是一個專門用來持有單一、最新狀態的熱流。它非常適合用來實作 MVVM 架構中的 ViewModel 狀態。

特性

  • 永遠有值:必須提供初始值。
  • 防抖動 (Conflated):如果新舊值相同 (enabled == enabled),不會發射更新。
  • 熱流:只要 Scope 存在,它就保持活躍。

在 ViewModel 中使用

class MainViewModel : ViewModel() {
    // 內部使用 MutableStateFlow 來更新狀態
    private val _uiState = MutableStateFlow(UiState.Loading)
    
    // 對外暴露唯讀的 StateFlow
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            val data = repository.fetchData()
            _uiState.value = UiState.Success(data) // 更新狀態
        }
    }
}

在 UI (Compose) 中觀察

在 Jetpack Compose 中,我們使用 collectAsStateWithLifecycle (推薦) 或 collectAsState 來觀察 StateFlow。

@Composable
fun MainScreen(viewModel: MainViewModel) {
    // 將 StateFlow 轉換為 Compose 的 State
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    when (state) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> Text("Data: ${(state as UiState.Success).data}")
    }
}
collectAsStateWithLifecycle 需要依賴 androidx.lifecycle:lifecycle-runtime-compose 套件。它會自動在 App 進入背景時停止收集,節省資源。

SharedFlow:事件流

SharedFlow 用於處理一次性事件 (One-shot Events),例如:顯示 Toast、導航跳轉、Snackbar 提示。

特性

  • 沒有初始值
  • 可以重播 (Replay):設定 replay 參數,讓新的訂閱者也能收到之前的 n 個事件。
  • 多對多:一個 SharedFlow 可以有多個發射者與收集者。

在 ViewModel 中使用

class MainViewModel : ViewModel() {
    private val _events = MutableSharedFlow<String>()
    val events = _events.asSharedFlow()

    fun showMessage(msg: String) {
        viewModelScope.launch {
            _events.emit(msg) // 發送事件
        }
    }
}

在 UI 中觀察

因為是單次事件,我們通常在 LaunchedEffect 中收集:

@Composable
fun MainScreen(viewModel: MainViewModel) {
    val context = LocalContext.current
    
    LaunchedEffect(Unit) {
        viewModel.events.collect { msg ->
            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
        }
    }
}

StateFlow vs SharedFlow vs LiveData

特性StateFlowSharedFlowLiveData
初始值必須有 (Always has value)無 (No initial value)可有可無
適用場景UI 狀態 (UI State)單次事件 (Events)UI 狀態 (舊專案)
重複值處理自動過濾相同值 (Distinct)不過濾不過濾
生命週期感知需搭配 flowWithLifecycle需搭配 flowWithLifecycle自動感知
執行緒任何 Coroutine Context任何 Coroutine Context僅限主執行緒

結論

  • UI 狀態:請使用 StateFlow。它完美替代了 LiveData,並且與 Coroutines 生態系整合得更好。
  • 一次性事件:請使用 SharedFlow。這解決了 LiveData "SingleLiveEvent" 的長期痛點。
  • 資料層:請使用普通的 Flow (Cold Stream)

掌握這三種流,你就能構建出響應式、高效且架構清晰的 Android 應用程式。