Android Compose UI Side Effects 副作用處理

在 Compose 的世界裡,Composable 函式最好是純函式 (Pure Function):輸入相同的參數,總是產生相同的 UI,且沒有副作用。

但現實世界充滿了副作用 (Side Effects):發送網路請求、啟動計時器、註冊接收器、彈出 Toast。如果不加控制,這些操作可能會在每次重組時被重複執行。

LaunchedEffect (一次性非同步任務)

當你想要在 Composable 進入畫面 (Enter Composition) 時 啟動一個 Coroutine。

@Composable
fun MyScreen(viewModel: MyViewModel) {
    // key1 = Unit 表示只執行一次
    LaunchedEffect(Unit) {
        viewModel.loadInitialData()
    }
}

如果 key 改變,原本的 Coroutine 會被取消,並重新啟動一個新的。

LaunchedEffect(userId) {
    // 當 userId 改變時,重新 fetch 資料
    viewModel.fetchUser(userId)
}

DisposableEffect (需要清理的任務)

當你需要在 Composable 離開畫面 (Leave Composition) 時 執行清理工作(如取消註冊 Callback)。

DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { ... }
    lifecycleOwner.lifecycle.addObserver(observer)

    // 這行必定要寫!
    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

SideEffect (發布 Compose 狀態到外部)

這比較少用。它是用來在重組成功後,將 Compose 的狀態「洩漏」給非 Compose 的系統(例如通知 Analytics 系統)。

SideEffect {
    // 每次重組成功都會執行
    analytics.logEvent("Screen Recomposed")
}

DerivedStateOf (衍生狀態)

當一個 State 頻繁改變(例如捲動位置),但你只關心它是否超過某個閾值時,使用 derivedStateOf 可以減少不必要的重組。

val listState = rememberLazyListState()

// 只有當 "是否大於 0" 的結果改變時,showButton 才會更新
val showButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}

小結

正確管理 Side Effects 對於 App 的穩定性至關重要。記住:永遠不要在 Composable 的大括號內直接執行副作用操作,一定要包在 Effect API 中。