안드로이드 시스템·Compose 용어 사전
네비게이션launchSingleTop · inclusive

popUpTo

이동하면서 백스택을 걷어내는 옵션. 로그인·탭 전환에서 필수.

이동하면서 백스택을 걷어내는 옵션.

// 로그인 성공 → 홈으로. 뒤로가기로 로그인에 돌아가면 안 된다
navController.navigate(Home) {
    popUpTo(Login) { inclusive = true }    // Login 까지 포함해 제거
}
  • inclusive = false — 그 목적지 '위' 만 걷어낸다 (목적지는 남는다)
  • inclusive = true — 그 목적지까지 함께 걷어낸다

왜 필요한가

navigate 만 쓰면 스택이 계속 쌓인다

  • [Login] → [Login, Home]
  • 뒤로가기 → 로그인 화면으로 돌아간다. 이미 로그인했는데

스플래시 · 온보딩 · 로그인은 전부 popUpTo 가 필요하다

하단 탭 — 표준 조합

navController.navigate(route) {
    popUpTo(navController.graph.findStartDestination().id) {
        saveState = true                   // 떠나는 탭의 상태를 저장
    }
    launchSingleTop = true                 // 같은 탭을 다시 눌러도 쌓지 않는다
    restoreState = true                    // 돌아오면 스크롤 위치까지 복원
}

이 세 줄이 없으면

  • 탭을 오갈 때마다 스택이 무한히 쌓인다
  • 뒤로가기를 20번 눌러야 앱이 닫힌다
  • 탭을 다시 열 때마다 스크롤이 맨 위로 간다

launchSingleTop

맨 위에 같은 목적지가 있으면 새로 쌓지 않고 재사용한다

알림을 연달아 누르거나 버튼을 빠르게 두 번 누를 때 같은 화면이 겹겹이 쌓이는 것을 막는다

스택을 직접 확인한다

// 디버깅용 — 현재 백스택을 로그로 본다
navController.addOnDestinationChangedListener { c, d, _ ->
    Log.d("NAV", c.currentBackStack.value.map { it.destination.route }.toString())
}

popBackStack의 반환값

val popped = navController.popBackStack()
if (!popped) finish()      // 스택이 비었으면 Activity 를 닫는다

popBackStack 은 걷어낼 것이 없으면 false 를 반환한다 무시하면 "뒤로가기가 아무 반응이 없는" 화면이 된다

면접 함정

  • "popUpTo는 화면을 지운다" → 백스택 엔트리를 제거한다. saveState = true면 상태는 보관된다.
  • "restoreState만 켜면 복원된다"saveState와 짝이어야 한다. 한쪽만 켜면 동작하지 않는다.

결과를 안전하게 되돌려 주기

// 보내는 쪽 (선택 화면)
navController.previousBackStackEntry?.savedStateHandle?.set("picked", id)
navController.popBackStack()

// 받는 쪽 — 한 번 소비하고 지운다
val entry = navController.currentBackStackEntry
LaunchedEffect(entry) {
    entry?.savedStateHandle?.getStateFlow<Long?>("picked", null)?.collect { id ->
        if (id != null) { apply(id); entry.savedStateHandle["picked"] = null }
    }
}

null 로 되돌리지 않으면 화면에 돌아올 때마다 같은 결과가 다시 적용된다 = 일회성 이벤트를 상태에 담았을 때와 똑같은 함정

조건부 시작 화면

// ❌ startDestination 을 조건으로 바꾸면 백스택이 꼬인다
NavHost(startDestination = if (loggedIn) Home else Login)

// ✅ 시작은 고정하고 이동으로 푼다
NavHost(startDestination = Splash) {
    composable<Splash> {
        LaunchedEffect(loggedIn) {
            navController.navigate(if (loggedIn) Home else Login) {
                popUpTo(Splash) { inclusive = true }
            }
        }
    }
}

startDestination 이 런타임에 바뀌면 NavHost 가 재생성되며 이미 쌓인 백스택과 어긋난다 — 로그인/로그아웃 시 흔한 버그

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Navigation — 화면 이동·백스택·타입 안전 경로