안드로이드 시스템·Compose 용어 사전
Compose부수효과 · side effect

LaunchedEffect

컴포지션에 들어올 때 코루틴을 시작하고, key가 바뀌거나 떠나면 취소하는 효과 API.

컴포지션에 들어올 때 코루틴을 시작하고, key가 바뀌거나 떠나면 취소한다.

@Composable
fun UserScreen(userId: Long, vm: UserViewModel) {
    LaunchedEffect(userId) {              // userId 가 바뀌면 이전 코루틴을 취소하고 새로 시작
        vm.load(userId)
    }
}

key가 전부다

  • LaunchedEffect(Unit) — 컴포지션에 들어올 때 한 번만. 떠나면 취소
  • LaunchedEffect(userId) — userId 가 바뀔 때마다 취소 후 재시작
  • LaunchedEffect(a, b) — 둘 중 하나라도 바뀌면 재시작
// ❌ 매 리컴포지션마다 취소·재시작된다
LaunchedEffect(vm.uiState) { ... }        // 상태 객체가 매번 새 인스턴스

// ❌ 값이 바뀌어도 반영되지 않는다
LaunchedEffect(Unit) { vm.load(userId) }  // 다른 사용자로 이동해도 안 다시 부른다

스낵바 · 스크롤처럼 화면에 묶인 작업

LaunchedEffect(state.errorMessage) {
    state.errorMessage?.let {
        snackbarHostState.showSnackbar(it)   // suspend 함수를 안전하게 부른다
        vm.onErrorShown()
    }
}

rememberUpdatedState — 재시작 없이 최신 값을 쓴다

@Composable
fun Splash(onTimeout: () -> Unit) {
    val current by rememberUpdatedState(onTimeout)
    LaunchedEffect(Unit) {                 // 타이머는 한 번만 돈다
        delay(3000)
        current()                          // 그런데 최신 콜백을 부른다
    }
}

onTimeout 을 key 로 주면 콜백 람다가 매번 새로 만들어져 타이머가 계속 리셋된다 key 없이 쓰면 옛 콜백을 붙잡는다

  • rememberUpdatedState 가 정확히 이 간극을 메운다

언제 쓰지 않나

버튼 클릭 같은 '사용자 이벤트' 에는 쓰지 않는다

  • LaunchedEffect 는 '컴포지션 진입/키 변경' 이 트리거다
  • 콜백 안에서는 rememberCoroutineScope 를 쓴다

비즈니스 데이터 로딩은 ViewModel 의 init 이나 stateIn 이 더 낫다

  • LaunchedEffect 는 화면 재진입마다 다시 돈다

면접 함정

  • LaunchedEffect에서 상태를 직접 바꾼다 → 그 상태를 읽는 곳이 다시 그려지고 다시 효과가 돌아 무한 루프가 될 수 있다.
  • "코루틴이니 아무 데서나 시작해도 된다" → 컴포지션을 떠날 때 취소되는 것이 핵심 가치다.

실행 순서

컴포지션 → 레이아웃 → 드로잉 → (프레임 커밋) → LaunchedEffect 블록 실행

즉, 첫 화면이 그려진 '뒤에' 돈다

  • 초기 데이터 로딩을 여기 두면 빈 화면이 한 프레임 보인다
  • ViewModel 의 init 이나 stateIn 이 더 빠르다

화면 재진입마다 다시 돈다

Navigation 으로 상세 → 목록 → 상세 로 오가면

  • LaunchedEffect(Unit) 이 매번 다시 실행된다
  • 로그를 두 번 찍고, API 를 두 번 친다

ViewModel 은 그 목적지 범위라 함께 재생성되지만, 상위 그래프 범위 ViewModel 이면 init 은 한 번만 돈다

  • "한 번만" 이 중요하면 ViewModel 쪽이 안전하다

무한 루프를 만드는 형태

// ❌ 효과가 상태를 바꾸고, 그 상태가 key 라서 효과가 다시 돈다
LaunchedEffect(count) { count++ }

// ❌ 매 리컴포지션마다 새 객체가 key 로 들어간다
LaunchedEffect(listOf(a, b)) { ... }     // listOf 가 매번 새 인스턴스

// ✅ 원시 값이나 안정적인 참조를 key 로
LaunchedEffect(a, b) { ... }

snapshotFlow — 상태를 Flow로

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .distinctUntilChanged()
        .collect { analytics.log("scrolled_to", it) }
}

Compose 상태를 Flow 연산자(debounce · distinctUntilChanged) 로 다루고 싶을 때 쓴다

함께 보면 좋은 용어

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