안드로이드 시스템·Compose 용어 사전
네비게이션NavHost · NavController · navigate

Navigation Compose

컴포저블 목적지를 그래프로 선언하고 백스택을 한 곳에서 관리하는 라이브러리.

컴포저블 목적지를 그래프로 선언하고 백스택을 한 곳에서 관리한다.

@Serializable data object Home
@Serializable data class Detail(val id: Long)      // 타입 안전 경로 (2.8+)

NavHost(navController, startDestination = Home) {
    composable<Home> {
        HomeScreen(onItemClick = { navController.navigate(Detail(it)) })
    }
    composable<Detail> { entry ->
        val args: Detail = entry.toRoute()
        DetailScreen(id = args.id, onBack = { navController.popBackStack() })
    }
}

왜 타입 안전 경로가 나왔나

예전 방식 — 문자열 경로 navController.navigate("detail/${item.id}") composable("detail/{itemId}") { it.arguments?.getLong("itemId") }

문제

  • 오타가 런타임에 드러난다
  • 인자 타입이 문자열 파싱에 묶인다
  • 인자를 추가하면 두 곳을 맞춰 고쳐야 한다

@Serializable 클래스를 경로로 쓰면 컴파일러가 전부 검사한다

인자는 ViewModel에서도 받을 수 있다

@HiltViewModel
class DetailViewModel @Inject constructor(handle: SavedStateHandle) : ViewModel() {
    private val args: Detail = handle.toRoute()
    val id = args.id
}

Composable 이 인자를 받아 ViewModel 에 넘기지 않아도 된다

  • 프로세스 죽음 뒤 복원도 SavedStateHandle 이 함께 처리한다

무엇을 인자로 넘기나

  • 넘겨도 되는 것 — id · 짧은 문자열 · enum 넘기면 안 되는 것 객체 전체 · 리스트 · 비트맵

  • **왜 — 경로는 URL 처럼 취급되고 상태 저장 Bundle 에도 들어간다**

    • 큰 객체를 넘기면 크기 한도에 걸리고 딥링크로도 표현할 수 없다
  • id 만 넘기고 목적지가 Repository 에서 다시 읽는다 (SSOT)

화면 간 결과 전달

// 결과를 되돌려 줄 때 — 이전 엔트리의 SavedStateHandle 을 쓴다
navController.previousBackStackEntry
    ?.savedStateHandle?.set("selected", itemId)
navController.popBackStack()

// 받는 쪽
val selected by navController.currentBackStackEntry
    ?.savedStateHandle?.getStateFlow<Long?>("selected", null)
    !!.collectAsStateWithLifecycle()

면접 함정

  • navController를 하위 컴포저블에 그대로 넘긴다 → 미리보기와 테스트가 어려워진다. 콜백(onItemClick)만 넘긴다.
  • "navigate를 빠르게 두 번 누르면 화면이 두 개 쌓인다" → 실제로 그렇다. launchSingleTop이나 클릭 디바운스가 필요하다.

함께 보면 좋은 용어

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