본문 바로가기
Android/Compose

[Compose] AI가 작성한 LaunchedEffect의 문제점, rememberUpdatedState

by 굿햄 2026. 7. 23.

필자는 GPT 5.5 xHigh를 사용하던 도중 발생한 문제에 대해 정리한다.

 

아래 예시는 기능상으로 문제가 드러나지 않아, 커밋 전 미처 확인하지 못했던 코드의 일부입니다.

AI가 생성한 초안에는 다음처럼 side-effect가 구현되어 있었습니다.

@Composable
private fun AuthEffectHandler(
    effect: Flow<Effect>,
    onEffect: (Effect) -> Unit,
) {
    val context = LocalContext.current

    LaunchedEffect(effect, context, backStack) {
        effect.collect { authEffect ->
            when (authEffect) {
                is Effect.ShowToast -> {
                    Toast.makeText(
                        context,
                        ...,
                        Toast.LENGTH_SHORT,
                    ).show()
                }

                Effect.NavigateToLoginEmail -> {
                    backStack.navigateToLoginEmail()
                }
            }
        }
    }
}

 

해당 구문에서 `LaunchedEffect`의 key는 실행중인 coroutine을 재시작 할 지를 결정하는 값입니다.

 

이 코드의 주 작업은 effect.collect로, 매개변수로 받은 Flow 인스턴스가 바뀌는 경우, 기존 collect를 취소한 뒤 새 Flow를 collect하는 것이 자연스럽습니다.

반대로 ViewModel이 동일한 Flow 인스턴스를 항상 제공한다는 보장이 있다면, key에서 제외하는 것도 고려할 수 있습니다.

 

Context, Back stack의 경우에도, 값이 바뀌었을 때 collection을 처음부터 재시작해야하는지, 실행중인 collector에서 최신 값만 참조하면 되는지를 판단하면 됩니다.

Context의 경우 화면 회전 등에 의해 기존 Activity가 파괴되어 메모리 누수가 발생할 수 있고, BackStack의 경우 보통 참조가 유지되지만 예상치 못한 케이스가 발생할 수 있습니다.

 

따라서 세 개의 key값을 rememberUpdatedState로 wrapping 하는 방법이 있으나, 코드가 길어지므로 이 상황에서는 다음과 같이 별도의 핸들러를 만들어 처리하는 것이 효율적으로 보입니다.

 

@Composable
private fun AuthRoute(
    effect: Flow<Effect>,
    backStack: NavBackStack,
) {
    val context = LocalContext.current
    val resources = LocalResources.current

    AuthEffectHandler(
        effect = effect,
        onEffect = { authEffect ->
            when (authEffect) {
                is Effect.ShowToast -> {
                    Toast.makeText(
                        context,
                        resources.getString(authEffect.messageResId),
                        Toast.LENGTH_SHORT,
                    ).show()
                }

                Effect.NavigateToLoginEmail -> {
                    backStack.navigateToLoginEmail()
                }
            }
        },
    )
}

@Composable
private fun AuthEffectHandler(
    effect: Flow<Effect>,
    onEffect: (Effect) -> Unit,
) {
    val currentOnEffect by rememberUpdatedState(onEffect) // onEffect 최신 로직 반영

    LaunchedEffect(effect) {
        effect.collect { authEffect ->
            currentOnEffect(authEffect)
        }
    }
}

 

댓글