(Android/Navigation) Navigation Component — 백스택 관리 전략


1. 백스택 기본 동작

navigate()를 호출할 때마다 Destination이 백스택에 쌓이고, 뒤로 가기는 하나씩 제거합니다.

Home → List → Detail      (스택: Home, List, Detail)
        뒤로 → List        (스택: Home, List)

문제는 뒤로 가도 안 되는 화면(로그인 완료 후 로그인 화면, 결제 완료 화면 등)이 스택에 남는 경우입니다.


2. popUpTo — 지정 지점까지 스택 제거

이동하면서 특정 Destination 위의 화면들을 제거합니다.

<action
    android:id="@+id/action_login_to_home"
    app:destination="@id/homeFragment"
    app:popUpTo="@id/loginFragment"
    app:popUpToInclusive="true" />
// 코드로 동일하게
navController.navigate(R.id.homeFragment, null, navOptions {
    popUpTo(R.id.loginFragment) { inclusive = true }
})
옵션 동작
popUpTo = X X 위의 화면을 제거 (X는 남음)
popUpToInclusive = true X 자신도 함께 제거

로그인 → 홈 이동에서 inclusive = true를 쓰면 뒤로 가기 시 로그인 화면으로 돌아가지 않습니다.


3. launchSingleTop — 중복 화면 방지

같은 화면이 스택 맨 위에 이미 있으면 새로 쌓지 않고 재사용합니다.

navController.navigate(R.id.searchFragment, null, navOptions {
    launchSingleTop = true
})
버튼을 빠르게 두 번 눌러도 Detail이 두 개 쌓이지 않음

빠른 더블 클릭으로 같은 화면이 중복 이동되는 문제의 간단한 방어책입니다.


4. saveState / restoreState — 탭 상태 보존

여러 흐름(탭)을 오가며 각 흐름의 스택을 보존할 때 사용합니다.

navController.navigate(destinationId, null, navOptions {
    popUpTo(navController.graph.startDestinationId) { saveState = true }
    launchSingleTop = true
    restoreState = true
})

Bottom Navigation의 setupWithNavController가 내부적으로 이 옵션을 사용합니다.


5. 중첩 그래프로 흐름 단위 관리

관련된 화면들을 하나의 흐름(로그인, 주문 등) 으로 묶으면 한 번에 제거하기 쉽습니다.

<navigation android:id="@+id/nav_graph" app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment" ... />

    <!-- 로그인 흐름: 3개 화면을 하나의 그래프로 -->
    <navigation
        android:id="@+id/auth_graph"
        app:startDestination="@id/loginFragment">
        <fragment android:id="@+id/loginFragment" ... />
        <fragment android:id="@+id/signUpFragment" ... />
        <fragment android:id="@+id/findPasswordFragment" ... />
    </navigation>
</navigation>
// 로그인 완료: auth_graph 전체를 스택에서 제거
navController.navigate(R.id.homeFragment, null, navOptions {
    popUpTo(R.id.auth_graph) { inclusive = true }
})

6. 흔한 시나리오별 처리

상황 처리
로그인 완료 후 홈 popUpTo(login/auth_graph) { inclusive = true }
스플래시 → 메인 popUpTo(splash) { inclusive = true }
결제 완료 → 주문 내역 결제 흐름 그래프 전체 popUpTo
로그아웃 전체 스택 제거 후 로그인으로 (popUpTo(graph.id) { inclusive = true })
같은 화면 중복 방지 launchSingleTop = true

7. 현재 스택 확인 (디버깅)

navController.addOnDestinationChangedListener { controller, destination, _ ->
    val stack = controller.currentBackStack.value
        .mapNotNull { it.destination.label }
    Log.d("Nav", "현재 스택: $stack")
}

8. 정리



Related Posts