안드로이드 시스템·Compose 용어 사전
Compose

rememberCoroutineScope

콜백(클릭 등)에서 코루틴을 시작하기 위한 컴포지션 수명 스코프.

콜백 안에서 코루틴을 시작하기 위한, 컴포지션에 묶인 스코프.

@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 한다

  • 반환값으로 '사용자가 실행 취소를 눌렀는지' 를 그대로 받는다
  • 콜백 지옥 없이 흐름이 직선으로 읽힌다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Compose 상태·side-effect — remember·LaunchedEffect