NavController — Jetpack Compose에서의 본질, 메서드 및 네비게이션 관리

저자: IT Sectr 게시일: 2026-06-29 읽는 시간: 7 분

NavController는 Navigation Compose 라이브러리의 핵심 구성 요소로, Android 애플리케이션에서 네비게이션 스택과 back stack 상태를 관리합니다. NavController를 통해 화면 간 전환, 이전 페이지로의 복귀, 라우트 간 데이터 전송이 수행됩니다. Android Developers (2025)에 따르면, NavController는 둘 이상의 화면이 있는 모든 Compose 애플리케이션의 필수 요소입니다. 컨트롤러는 rememberNavController()를 통해 생성되고 NavHost에 전달되며 컴포지션의 모든 지점에서 navigate()를 호출할 수 있습니다. 내장된 SavedStateHandle 지원은 재구성 중에 ViewModel 상태를 자동으로 저장합니다.

핵심 사항

  • NavController — Compose의 중앙 네비게이션 컨트롤러, back stack 및 화면 간 전환 관리
  • navigate() — 스택 관리를 위한 NavOptions 지원과 함께 라우트로 이동하는 주요 메서드
  • popBackStack() — 지정된 라우트까지 선택적 정리와 함께 이전 화면으로 복귀
  • SavedStateHandle — 네비게이션 중 화면 상태 보존을 위한 ViewModel과의 통합
  • currentBackStackEntryAsState() — UI 동기화를 위한 현재 라우트 관찰

Jetpack Compose에서 NavController란?

NavController는 Navigation Compose 라이브러리의 클래스로, Compose 애플리케이션을 위한 네비게이션 컨트롤러를 구현합니다. NavController는 NavBackStackEntry 스택을 관리하며, 각 항목에는 라우트, 인수 및 화면 상태가 포함됩니다. 컨트롤러는 기본 네비게이션 작업(전환, 복귀, 교체, 정리)을 지원합니다.

View 시스템(FragmentManager 또는 Intent를 통한 네비게이션)과 달리, NavController는 Compose 컨텍스트에서만 작동합니다. Back stack은 Fragment 스택 대신 NavDestination 그래프로 저장됩니다. 이는 Fragment 생성 및 제거의 오버헤드를 제거하고 테스트를 간소화합니다. NavController는 TestNavHostController를 통해 모킹할 수 있습니다.

NavController는 NavHost와 밀접하게 연결되어 있습니다. NavHost는 그래프에서 현재 화면을 렌더링하는 컨테이너입니다. NavHost 없이 NavController는 composable 함수를 표시할 수 없지만 스택을 관리하는 기능은 유지합니다. 일반적인 아키텍처에서 NavController는 Activity 또는 메인 composable 수준에서 생성되며 매개변수를 통해 컴포지션 트리 아래로 전달됩니다.

Google에 따르면, NavController는 여러 주요 릴리스를 거쳤습니다. 버전 2.8.0은 Type-Safe Navigation을 추가했고, 버전 2.9.0은 predictive back gesture (Android 14+) 지원을 추가했습니다. 컨트롤러는 Material3 Scaffold 및 BottomNavigation과 호환됩니다. 멀티 모듈 프로젝트의 경우 NavController는 DI(Hilt/Koin) 또는 생성자 매개변수를 통해 전달됩니다.

NavController는 composable 함수 rememberNavController()를 통해 생성됩니다. 이 함수는 현재 composable의 수명 주기에 바인딩된 NavHostController(NavController의 하위 클래스) 인스턴스를 반환합니다. 컴포지션을 떠나면 컨트롤러가 정리됩니다. 재구성 중에 컨트롤러를 보존하려면 rememberSaveable 또는 ViewModel을 사용하세요.

kotlin
@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "main"
    ) {
        composable("main") { MainScreen(navController) }
        composable("details") { DetailsScreen(navController) }
    }
}

NavController 구성에는 다음이 포함됩니다: NavHostController(기본), TestNavHostController(테스트), ScopedNavController(중첩 그래프용 자식). BottomNavigation의 경우 NavController는 전체 앱에서 단일해야 합니다. 각 탭에서 새 컨트롤러를 만들면 스택이 손실됩니다. 중첩된 화면에 컨트롤러를 전달하려면 가독성을 유지하기 위해 CompositionLocalProvider 대신 함수 매개변수를 사용하세요.

네비게이션 테스트를 위해 compose-test-rule과 함께 TestNavHostController를 사용하세요. 컨트롤러를 사용하면 초기 라우트를 설정하고 navigate()가 예상된 전환을 트리거했는지 확인할 수 있습니다. NavController 테스트에 에뮬레이터는 필요하지 않으며 Compose Test Semantics 매처로 작동합니다.

navigate(route: String) 메서드는 NavController의 기본 네비게이션 메커니즘입니다. 라우트 문자열, 선택적 NavOptions 및 Navigator.Extras를 허용합니다. NavOptions는 전환 동작을 제어합니다: launchSingleTop(스택에서 라우트 중복 방지), popUpTo(지정된 라우트까지 스택 정리), restoreState(이전 상태 복원).

NavOptions는 빌더 구문(NavOptionsBuilder)을 통해 설정됩니다. 주요 매개변수: popUpTo(라우트 + inclusive/saveState), launchSingleTop(Boolean, true — 중복 생성 안 함), restoreState(복귀 시 상태 복원). popUpTo 없이 각 navigate()는 스택에 항목을 추가하여 back stack이 누적되고 Back 버튼 동작이 잘못됩니다.

kotlin
navController.navigate("profile/42") {
    popUpTo("main") { saveState = true }
    launchSingleTop = true
    restoreState = true
}

Navigator.Extras를 사용하면 라우트의 일부가 아닌 추가 데이터(애니메이션용 공유 요소, Intent 플래그, Pac-Man 번들)를 전달할 수 있습니다. Extras는 거의 사용되지 않으며 주로 Accompanist Animation 또는 사용자 정의 Navigator와의 통합에 사용됩니다. 대부분의 시나리오에서는 라우트 문자열과 NavOptions로 충분합니다.

popBackStack: 복귀 및 스택 정리 관리

popBackStack()은 이전 화면으로 복귀하는 메서드입니다. 인수 없이 스택의 최상위 항목을 제거하고 성공하면 true를 반환합니다. 스택이 비어 있으면 메서드는 false를 반환하고 Activity가 닫힙니다(super.onBackPressed()와 유사).

오버로드된 버전 popBackStack(route: String, inclusive: Boolean)은 지정된 라우트까지 모든 항목을 제거합니다. inclusive = true이면 지정된 라우트 자체도 제거됩니다. 메서드는 Boolean을 반환합니다. 항목을 찾아 제거하면 true입니다. inclusive 버전은 인증 또는 주문 완료 후 “루트 화면으로 종료” 시나리오에 유용합니다.

메서드설명예제
popBackStack()한 화면 뒤로 복귀navController.popBackStack()
popBackStack(route, false)route까지 정리(route 유지)popBackStack(“home”, false)
popBackStack(route, true)route까지 포함하여 정리popBackStack(“home”, true)
navigate(route) { popUpTo(route) { inclusive = true } }전체 정리와 함께 이동navigate(“login”) { popUpTo(0) { inclusive = true } }

시스템 Back 버튼(하드웨어 백 버튼)을 처리하려면 Compose의 BackHandler를 사용하세요. BackHandler는 enabled와 onBack(누를 때 호출되는 콜백)을 허용합니다. Android 14+의 경우 NavController 버전 2.9.0부터 통합된 PredictiveBackGesture가 사용됩니다. Predictive back은 복귀 미리보기 애니메이션을 추가합니다.

SavedStateHandle: 화면 상태 보존

SavedStateHandle은 네비게이션 및 재구성 중에 ViewModel 상태를 보존하는 메커니즘입니다. NavController는 각 NavBackStackEntry에 SavedStateHandle을 자동으로 제공합니다. SavedStateHandle을 통해 ViewModel은 화면 상태를 저장하고 복귀 시 복원합니다(restoreState = true).

Navigation Compose에서 SavedStateHandle은 ViewModel과 함께 사용됩니다. ViewModel은 backStackEntry에서 전달된 SavedStateHandle을 통해 초기화됩니다. 다른 화면으로 이동했다가 복귀할 때(restoreState 사용), ViewModel은 새로 생성되는 대신 저장된 상태를 받습니다. 이는 데이터 입력, 필터 또는 스크롤이 있는 화면에 중요합니다.

kotlin
class ProfileViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    val userId: String = savedStateHandle.get<String>("userId") ?: ""
    var searchQuery by savedStateHandle.getStateFlow("search", "")
        .collectAsState()
}

SavedStateHandle은 기본 유형, String, Bundle 및 Parcelable을 지원합니다. 복잡한 객체의 경우 ID만 저장하고 리포지토리에서 전체 데이터를 로드합니다. SavedStateHandle의 제한은 약 1MB이며 초과하면 TransactionTooLargeException이 발생합니다. 대용량 데이터의 경우 handle에 저장하는 대신 Room 또는 DataStore를 사용하세요.

중요: SavedStateHandle은 NavOptions에서 restoreState = true가 사용된 경우에만 상태를 보존합니다. restoreState가 지정되지 않으면 복귀 시 ViewModel이 기본값으로 새로 생성됩니다. restoreState로 BottomNavigation 전환 시 NavController는 각 탭의 상태를 보존하고 재선택 시 복원합니다.

currentBackStackEntryAsState를 통한 현재 라우트 관찰

currentBackStackEntryAsState()는 State<NavBackStackEntry?>를 반환하는 함수로, 현재 라우트가 변경될 때마다 업데이트됩니다. 이는 네비게이션과 UI를 동기화하는 주요 메커니즘입니다. BottomNavigation은 활성 항목을 강조 표시하고, Toolbar는 제목을 업데이트하며, Drawer는 전환 시 닫힙니다.

함수는 snapshotFlow와 collectAsState를 통해 작동합니다. back stack이 변경되면 Compose가 구독된 요소를 재구성합니다. 중요: currentBackStackEntryAsState()는 전환 애니메이션이 완료된 후에만 업데이트됩니다. 즉시 업데이트가 필요한 경우 navigate()와 동기적으로 변경되지만 상태를 지원하지 않는 currentDestination을 사용하세요.

kotlin
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route

Text(
    text = when (currentRoute) {
        "home" -> "Home"
        "profile" -> "Profile"
        else -> ""
    }
)

현재 라우트의 인수에 액세스하려면 navBackStackEntry?.arguments를 사용하세요. 이는 BottomNavigation에서 편리합니다. selectedItem은 currentRoute를 기반으로 계산됩니다. 네비게이션 디버깅을 위해 각 전환을 기록하는 NavController.addOnDestinationChangedListener()를 사용하세요. 프로덕션에서는 많은 수의 composable 내에서 구독을 피하고 ViewModel에서 단일 소스를 만들어 State를 UI에 전달하세요.

자주 묻는 질문

하나의 Activity에서 여러 NavController를 만들 수 있나요?

기술적으로는 가능하지만 권장되지 않습니다. 단일 NavController는 일관된 back stack을 보장하고 디버깅을 간소화합니다. 여러 컨트롤러는 별도의 네비게이션이 있는 중첩 그래프(예: 자체 스택이 있는 modal bottom sheet)에서만 정당화됩니다.

ViewModel을 통해 NavController를 전달하는 방법은?

생성자 또는 DI를 통해 NavController를 ViewModel에 전달하세요. 그러나 NavController 자체보다는 콜백 함수(onNavigate, onBack)만 전달하는 것이 테스트를 간소화하므로 더 좋습니다. 이벤트의 경우 ViewModel에서 Channel<NavEvent>를 사용하고 UI에서 수집하세요.

비동기 작업 후 navigate가 작동하지 않는 이유는?

문제는 수명 주기에 있습니다. NavController가 아직 초기화되지 않은 경우(NavHost가 구축되지 않음) navigate()가 무시됩니다. 데이터 로드 후 네비게이션을 호출하려면 임의의 수명 주기를 가진 코루틴 내부가 아닌 LaunchedEffect를 사용하세요.

전체 back stack을 정리하고 새 화면으로 이동하는 방법은?

navController.navigate(“target”) { popUpTo(0) { inclusive = true } }를 호출하세요. popUpTo(0) 매개변수는 스택을 완전히 정리하고 inclusive = true는 시작 항목도 제거합니다. launchSingleTop = true 플래그는 중복 라우트를 방지합니다.

NavHostController와 NavController의 차이점은?

NavHostController는 NavController의 하위 클래스로 NavHost를 위한 추가 메서드(setOnBackStackChangedListener 등)가 있습니다. NavController는 NavHost 외부에서 프로그래밍 방식 스택 관리에 사용할 수 있는 기본 클래스입니다. 대부분의 경우 NavHostController가 사용됩니다.

요약

  • NavController — Navigation Compose의 핵심 구성 요소, 라우트 스택 및 화면 간 전환 관리
  • navigate() — NavOptions를 통한 popUpTo, launchSingleTop 및 restoreState 설정으로 전환 수행
  • popBackStack() — 복귀 관리: 단일 단계 또는 inclusive와 함께 지정된 라우트까지 일괄 정리
  • SavedStateHandle — 네비게이션 중 자동 화면 상태 보존을 위해 ViewModel과 통합
  • currentBackStackEntryAsState() — UI 동기화를 위한 현재 라우트의 반응형 관찰 제공
  • BackHandler — 시스템 Back 버튼 처리, PredictiveBackGesture는 NavController 2.9.0부터 지원
  • 테스트를 위해 compose-test-rule 및 Semantics 매처와 함께 TestNavHostController 사용

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기