Compose 상태·side-effect — remember·LaunchedEffect·effect API
[03]에서 컴포저블은 순수해야 한다고 했다. 그런데 화면에 들어올 때 데이터를 불러오거나, 스낵바를 띄우거나, 리소스를 정리하는 *부수효과(side-effect)*는 어딘가서 해야 한다. Compose는 이를 위한 effect API(LaunchedEffect·DisposableEffect·rememberCoroutineScope 등)를 제공한다 — recomposition과 무관하게, 생명주기에 맞춰 부수효과를 통제된 방식으로 실행한다. (출처: Android — Side-effects in Compose.)
remember와 rememberSaveable
remember { }— recomposition 사이에 값을 보존(계산 캐시·상태 보관). 구성 변경(회전)엔 사라진다.rememberSaveable { }— 회전·프로세스 죽음에도 Bundle로 저장돼 복원([andarch 07]).key—remember(key) { }처럼 키가 바뀌면 다시 계산한다.
val sorted = remember(items) { items.sortedDescending() } // items 바뀔 때만 재계산
var name by rememberSaveable { mutableStateOf("") } // 회전에도 입력 보존
LaunchedEffect — 컴포지션에 들어올 때 suspend 작업
**LaunchedEffect(key)**는 컴포저블이 컴포지션에 들어올 때 코루틴을 시작하고, 떠나면 취소한다. 키가 바뀌면 이전 걸 취소하고 다시 시작한다. 화면 진입 시 데이터 로드·스낵바·애니메이션 등에 쓴다.
@Composable
fun DetailScreen(id: Long, vm: DetailViewModel) {
LaunchedEffect(id) { // id가 바뀌면 이전 취소 후 재시작
vm.load(id) // suspend 호출 — 진입 시 1회(키 고정이면)
}
val state by vm.uiState.collectAsStateWithLifecycle()
// ...
}
DisposableEffect — 정리(cleanup)가 필요한 효과
**DisposableEffect**는 효과를 시작하고, 컴포지션을 떠날 때 정리한다 — 리스너 등록/해제·콜백 등록/해제처럼 짝이 있는 작업에.
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, e -> /* ... */ }
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } // 떠날 때 해제
}
rememberCoroutineScope — 콜백에서 코루틴 시작
LaunchedEffect는 컴포지션 진입에 묶이지만, 버튼 클릭 같은 콜백에서 코루틴을 시작하려면 **rememberCoroutineScope**를 쓴다.
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { snackbar.showSnackbar("저장됨") } }) { Text("저장") }
그 밖의 effect·상태 API
derivedStateOf— 다른 상태에서 파생된 값을 그 결과가 바뀔 때만 재계산(불필요한 recomposition 억제). 예: 스크롤 위치 → "맨 위인가" 불리언.snapshotFlow— Compose 상태를 Flow로 변환(상태 변화를 스트림으로).produceState— 비-Compose 소스(콜백·Flow)를 Compose 상태로.rememberUpdatedState— 오래 실행되는 효과 안에서 최신 값을 참조(LaunchedEffect 재시작 없이).SideEffect— recomposition이 성공할 때마다 비-Compose 객체에 값 전달.
정리 — 부수효과를 통제된 자리에
Compose 부수효과의 핵심 — 계산·상태 보존은 remember/rememberSaveable, 진입 시 suspend 작업은 LaunchedEffect(key)(이탈 시 취소), 정리가 필요하면 DisposableEffect(onDispose), 콜백에서 코루틴은 rememberCoroutineScope, 파생 값은 derivedStateOf. 컴포저블 본문은 순수하게 두고, 부수효과는 이 API들로 — 그래야 recomposition이 반복돼도 안전하다.
한 줄 요약 —
remember(보존·캐시, key로 무효화)/rememberSaveable(회전·프로세스 죽음). 진입 suspend는LaunchedEffect(key)(이탈 취소), 정리는DisposableEffect(onDispose), 콜백 코루틴은rememberCoroutineScope.derivedStateOf(파생값 최적화)·snapshotFlow·produceState. 본문은 순수, 효과는 API로.
(출처: Android — Side-effects in Compose / State and Jetpack Compose / State in Compose.)