|

Compose LaunchedEffect key가 바뀌면 왜 계속 재실행될까

Compose LaunchedEffect key 재실행 구조 대표 이미지
LaunchedEffect는 key가 바뀌면 기존 coroutine을 취소하고 새 effect를 시작한다.

LaunchedEffect key는 Compose side effect를 이해할 때 가장 먼저 헷갈리는 지점입니다. 화면이 다시 그려질 때마다 API 호출이 반복되거나, 타이머가 다시 시작되는 문제로 이어지기 쉽습니다.

핵심은 LaunchedEffect의 key는 effect를 다시 시작할 조건이라는 점입니다. key가 바뀌면 기존 coroutine은 취소되고 새 coroutine이 시작됩니다.


LaunchedEffect key 핵심 기준

Compose LaunchedEffect key 판단 카드
LaunchedEffect의 key는 값 전달이 아니라 재시작 조건에 가깝다.

LaunchedEffect는 언제 시작될까

Android Developers의 Side-effects in Compose 문서는 LaunchedEffect가 Composition에 들어오면 coroutine을 시작한다고 설명합니다. 그리고 LaunchedEffect가 Composition을 떠나면 coroutine은 취소됩니다.

또 key가 달라진 상태로 recomposition되면 기존 coroutine을 취소하고 새 suspend block을 실행합니다. 그래서 key는 단순 인자가 아니라 effect의 수명 조건입니다.

key 안정성은 리스트에서도 중요합니다. Compose LazyColumn key 글과 함께 보면 Compose가 상태와 식별자를 어떻게 연결하는지 더 잘 보입니다.

자주 생기는 실수: 바뀌는 객체를 key로 넣는다

@Composable
fun SearchScreen(filter: SearchFilter) {
    LaunchedEffect(filter) {
        viewModel.search(filter)
    }
}

이 코드에서 filter 객체가 recomposition마다 새로 만들어지면 값이 같아 보여도 key가 바뀐 것으로 처리될 수 있습니다. 그러면 검색 요청이 계속 다시 실행됩니다.

key는 재시작되어야 하는 값만 넣는다

사용자가 검색어를 바꾸면 다시 검색해야 합니다. 하지만 단순 callback이나 현재 lambda 값이 바뀌었다는 이유로 effect를 재시작할 필요는 없을 수 있습니다.

@Composable
fun SearchScreen(query: String) {
    LaunchedEffect(query) {
        viewModel.search(query)
    }
}

이 경우 key는 명확합니다. query가 바뀌면 다시 검색하고, query가 같으면 effect를 유지하는 것이 자연스럽습니다.

rememberUpdatedState는 언제 쓸까

공식 문서는 값 변경으로 effect를 재시작하고 싶지 않지만 effect 안에서 최신 값을 참조해야 할 때 rememberUpdatedState를 사용할 수 있다고 설명합니다.

@Composable
fun TimeoutScreen(onTimeout: () -> Unit) {
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    LaunchedEffect(Unit) {
        delay(3_000)
        currentOnTimeout()
    }
}

여기서 onTimeout이 바뀔 때마다 delay를 처음부터 다시 시작하면 UX가 이상해질 수 있습니다. 그래서 effect는 유지하고, callback만 최신 값으로 참조합니다.

LaunchedEffect(Unit)은 안전할까

LaunchedEffect(Unit)이나 LaunchedEffect(true)는 한 번만 실행하고 싶을 때 자주 보입니다. 하지만 공식 문서도 LaunchedEffect(true)while(true)처럼 의심스럽다고 경고합니다.

정말 화면 진입 시 한 번이면 충분한지, navigation back stack 복귀나 process recreation 때 다시 실행되어도 괜찮은지 확인해야 합니다.

점검 체크리스트

  • 이 값이 바뀌면 effect를 반드시 다시 시작해야 하는가?
  • 객체 전체가 아니라 안정적인 id나 query만 key로 둘 수 있는가?
  • callback 최신 값만 필요하다면 rememberUpdatedState가 맞지 않은가?
  • API 호출은 ViewModel state와 중복으로 실행되지 않는가?
  • LaunchedEffect(Unit)이 화면 재진입에서 문제를 만들지 않는가?

정리

LaunchedEffect key는 effect에 전달하는 데이터가 아니라 effect의 재시작 조건입니다. 계속 재실행되는 문제가 있다면 recomposition 자체보다 key가 실제로 언제 바뀌는지 먼저 봐야 합니다.

함께보면 좋은 글