Android Flow 非同步資料流

suspend 函式一次只能回傳一個值。如果我們要處理「一連串」的非同步資料(例如:每秒更新的股票價格、下載進度、GPS 即時位置),該怎麼辦?

這就是 Kotlin Flow 登場的時刻。Flow 是基於 Coroutines(協程)構建的「非同步資料流」解決方案,專門用來處理連續發送的數據(例如資料庫更新、感測器數據或網路狀態),更輕量且易於使用,類似於 RxJava 的 Observable。

Flow 建立在 Coroutines 之上,因此當協程被取消時,Flow 也會自動停止,不會造成記憶體洩漏。

Flow 的基本結構:三個角色

Flow 的運作流程可以簡化為「生產者、中介者、消費者」的關係:

  • Producer (生產者):負責發送(emit)資料到 Flow 中。通常使用 flow { ... } 構建器。
  • Intermediaries (中介者):對資料流進行加工。例如 map(轉換資料)、filter(過濾資料)。
  • Consumer (消費者):接收並處理資料。通常使用 collect 來觸發 Flow 開始執行。

什麼是 Flow?

Flow 是冷流 (Cold Stream)。這意味著 Flow 中的程式碼:

  • 程式碼塊在被「收集 (collect)」之前不會執行。
  • 每個收集者都會得到一份獨立的資料流。
  • 這就像「YouTube 影片」,只有當有人點擊播放(collect)時,內容才會開始播放。

這跟 Sequence 很像,但適用於非同步操作。

建立 Flow

使用 flow { ... } 建構器:

fun simpleFlow(): Flow<Int> = flow {
    println("Flow started")
    for (i in 1..3) {
        delay(100) // 模擬耗時操作
        emit(i)    // 發射資料
    }
}
  • emit(value):用來發射資料給下游。
  • flow { ... } 內的程式碼是可掛起的 (suspendable)。

收集 Flow (Collect)

要取得 Flow 的資料,只有呼叫終端運算子 (Terminal Operator) Flow 才會開始執行,最常用的是 collect。且此動作必須在 Coroutine 中執行。

scope.launch {
    simpleFlow().collect { value ->
        println("Received $value")
    }
}

常用運算子 (Operators)

Flow 擁有豐富的運算子,可以用宣告式的方式轉換資料。

中介運算子 (Intermediate Operators)

這些運算子不會觸發執行,只是設定轉換規則。

  • map:轉換資料型態。
  • filter:過濾資料。
  • onEach:對每個元素執行操作(不改變資料流),常用於 logging。
  • flowOn:切換上游執行的 Dispatcher。
simpleFlow()
    .filter { it % 2 == 0 } // 只取偶數
    .map { "Number $it" }   // 轉成字串
    .flowOn(Dispatchers.IO) // 上游操作在 IO 執行緒執行
    .collect { println(it) } // 在當前執行緒收集結果

終端運算子 (Terminal Operators)

這些運算子會啟動 Flow 的執行。

  • collect:收集所有資料。
  • first:只取第一個值(然後取消 Flow)。
  • toList:將所有資料收集成一個 List。
  • single:預期 Flow 只有一個值。

Exception Handling (例外處理)

Flow 的例外處理亦遵循結構化並發。

catch 運算子

使用 catch 運算子來捕獲上游拋出的例外:

flow {
    emit(1)
    throw RuntimeException("Boom!")
}
.catch { e ->
    emit(-1) // 發生錯誤時發射一個預設值
}
.collect { ... }

onCompletion

無論 Flow 是正常結束、被取消、還是發生錯誤,onCompletion 都會被執行(類似 finally)。

flow { ... }
.onCompletion { cause ->
    if (cause != null) println("Flow completed explicitly")
}
.collect { ... }

Flow vs LiveData

在早期的 Android 開發中,我們大量使用 LiveData。現在 Google 官方建議:

  • ViewModel 層與 Data 層:全面使用 Flow
  • UI 層 (Activity/Fragment):觀察 Flow,或者將 Flow 轉換為 LiveData (為了相容舊程式碼)。

Flow 的優勢在於它擁有 Coroutine 的完整支援,以及豐富的運算子,能處理複雜的資料轉換邏輯。而 LiveData 功能相對單一,主要僅用於感知生命週期的資料持有。

在下一章,我們將介紹專門為 UI 狀態管理設計的 Flow 變體:StateFlowSharedFlow