콜백 안에서 코루틴을 시작하기 위한, 컴포지션에 묶인 스코프.
@Composable
fun ScrollToTopButton(listState: LazyListState) {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch { listState.animateScrollToItem(0) } // suspend 함수를 콜백에서
}) { Text("맨 위로") }
}
왜 LaunchedEffect로는 안 되나
-
LaunchedEffect — 트리거가 '컴포지션 진입 / key 변경' 이다
- 클릭 순간에 시작할 수 없다
-
rememberCoroutineScope — 트리거가 '내가 부르는 시점' 이다
- 대신 취소 시점은 컴포지션 이탈이다
판별
화면에 들어오면 자동으로 시작 → LaunchedEffect
사용자가 눌러야 시작 → rememberCoroutineScope
어느 스코프에 묶이나
이 컴포저블이 컴포지션을 떠나면 스코프가 취소된다
- 화면을 벗어나면 진행 중이던 스크롤 애니메이션이 멈춘다 (원하는 동작)
주의: 화면이 사라져도 끝나야 하는 작업(주문 제출) 을 여기 두면 안 된다
- 그건 viewModelScope 다
// ❌ 화면을 나가면 제출이 취소된다
Button(onClick = { scope.launch { repository.submitOrder(order) } })
// ✅ ViewModel 이 소유한다
Button(onClick = { viewModel.submit(order) })
// class OrderViewModel { fun submit(o: Order) = viewModelScope.launch { ... } }
스낵바 · 바텀시트 · 드로어
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
IconButton(onClick = { scope.launch { drawerState.open() } }) { ... }
Compose 의 UI 제어 API 는 대부분 suspend 다
- animateScrollToItem · showSnackbar · drawerState.open · sheetState.expand
- 애니메이션이 끝날 때까지 기다릴 수 있게 하려는 설계
- 그래서 콜백에서 부르려면 스코프가 필요하다
면접 함정
- ❌
CoroutineScope(Dispatchers.Main)을 직접 만든다 → 컴포지션 이탈 시 취소되지 않아 누수다. - ❌ "컴포저블 본문에서 scope.launch를 부른다" → 리컴포지션마다 새 코루틴이 뜬다. 콜백 안에서만 부른다.
어떤 컨텍스트를 갖나
호출된 지점의 컴포지션 CoroutineContext 를 물려받는다 기본 디스패처는 AndroidUiDispatcher.Main 이다
- launch { } 안에서 바로 UI 상태를 바꿔도 안전하다
- 무거운 일은 안에서 withContext(Dispatchers.IO) 로 옮긴다
여러 번 눌렀을 때
// ❌ 빠르게 세 번 누르면 코루틴이 세 개 뜬다
Button(onClick = { scope.launch { listState.animateScrollToItem(0) } })
// ✅ 진행 중이면 무시하거나 이전 것을 취소한다
var job by remember { mutableStateOf<Job?>(null) }
Button(onClick = {
job?.cancel()
job = scope.launch { listState.animateScrollToItem(0) }
})
스낵바 큐잉
val host = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
val result = host.showSnackbar("삭제했습니다", actionLabel = "실행 취소")
if (result == SnackbarResult.ActionPerformed) viewModel.undo()
}
})
showSnackbar 는 스낵바가 사라질 때까지 suspend 한다
- 반환값으로 '사용자가 실행 취소를 눌렀는지' 를 그대로 받는다
- 콜백 지옥 없이 흐름이 직선으로 읽힌다