搭配生命週期感知元件使用 Kotlin 協同程式

Kotlin 協同程式提供的 API 可讓您編寫非同步程式碼。您可以透過 Kotlin 協同程式定義 CoroutineScope,協助管理協同程式執行的時間。每項非同步作業都會在特定範圍內執行。

生命週期感知元件為應用程式中的邏輯範圍提供一流的協同程式支援。本文說明如何搭配生命週期感知元件有效使用協同程式。

新增依附元件

本主題說明內建協同程式範圍,均包含在 Lifecycle API 中。使用這些範圍時,請務必新增適當的依附元件。

  • 如要在 Compose 中使用 ViewModel 公用程式,請使用 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
  • 如要使用 Compose 中的生命週期公用程式,請使用 implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")

生命週期感知協同程式範圍

Compose 和 Lifecycle 程式庫提供下列內建範圍,可在應用程式中使用。

ViewModelScope

應用程式中的每個 ViewModel 都有一個 ViewModelScope。如果 ViewModel 已清除,這個範圍中啟動的所有協同程式都會自動取消。如果有工作需要在 ViewModel 啟用時處理,則協同程式就相當實用。舉例來說,如要計算版面配置的某些資料,須將工作範圍設為 ViewModel,這樣在 ViewModel 清除後,工作就會自動取消,以免消耗資源。

您可以透過 ViewModelviewModelScope 屬性存取 ViewModelCoroutineScope,如以下範例所示:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

如有更進階的需求,您可以將自訂 CoroutineScope 直接傳遞至 ViewModel 的建構函式,取代預設的 viewModelScope。這種做法可提供更多控制權和彈性,特別適合:

  • 測試:您可以插入 TestScope,在單元測試中更輕鬆地控管時間和驗證協同程式行為。

  • 自訂設定:您可以在 ViewModel 開始工作前,使用特定 CoroutineDispatcher (例如用於大量運算的 Dispatchers.Default) 或自訂 CoroutineExceptionHandler 設定範圍。

組合繫結範圍

動畫、網路呼叫或計時器等副作用必須限定在可組合項的生命週期內。這樣一來,當可組合函式離開畫面 (結束組合) 時,系統就會自動取消所有執行中的協同程式,避免發生記憶體外洩。

Compose 提供 LaunchedEffect API,以宣告方式處理組合範圍。

LaunchedEffect 會建立 CoroutineScope,讓您執行暫停函式。這個範圍會繫結至可組合項的組合生命週期,而非主機活動的生命週期。

  • 輸入:可組合函式進入組合時,協同程式就會啟動。
  • 結束:可組合函式離開組合時,協同程式就會取消。
  • 重新啟動:如果傳遞至 LaunchedEffect 的任何鍵有所變更,系統會取消現有的協同程式,並啟動新的協同程式。

以下範例說明如何使用 LaunchedEffect 建立脈衝動畫。協同程式會繫結至可組合函式在組合中的存在狀態,並對設定變更做出回應:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

如要進一步瞭解 LaunchedEffect,請參閱「Compose 中的連帶效果」。

生命週期感知 Flow 集合

如要在 Jetpack Compose 中安全地收集資料流,請使用 collectAsStateWithLifecycle API。這個單一函式會將 Flow 轉換為 Compose State 物件,並自動管理生命週期訂閱項目。根據預設,系統會在生命週期為 STARTED 時開始收集,並在生命週期為 STOPPED 時停止收集。如要覆寫這項預設行為,請傳入 minActiveState 參數,並指定所需的生命週期方法,例如 Lifecycle.State.RESUMED

以下範例說明如何在可組合函式中收集 ViewModel 的 StateFlow

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

平行收集多個流程

在 Compose 中,您可以宣告多個狀態變數,平行收集多個 Flow。由於 collectAsStateWithLifecycle 會管理自己的基礎範圍,因此系統會自動處理平行收集作業:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

使用 Flow 非同步計算值

如要非同步計算值,請搭配 stateIn 運算子使用 StateFlow

下列程式碼片段使用轉換為 StateFlow 的標準 FlowWhileSubscribed(5000) 參數會在 UI 消失後,讓訂閱項目維持有效五秒,以處理設定變更。

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

使用 collectAsStateWithLifecycle 將收集到的值轉換為 Compose State,這樣每當資料變更時,UI 就能做出反應並更新。

如要進一步瞭解狀態,請參閱「狀態和 Jetpack Compose」。

其他資源

Views content

範例