Jetpack Navigation Component: The Complete Guide

The Jetpack Navigation Component standardizes navigation in Android apps. Whether you are using Fragments, Compose, or a mix, Navigation gives you a single source of truth for your app's navigation graph, type-safe argument passing, and deep link support out of the box.

Core Concepts

  • NavGraph — a graph of destinations and the actions that connect them. Defined in XML or in code (Compose).
  • NavController — the engine that drives navigation. One per NavHost.
  • NavHost — the container that displays the current destination.

Navigation in Compose

For pure-Compose apps, use the navigation-compose artifact. Define your graph in a composable function using the NavHost DSL.

val navController = rememberNavController()

NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen(navController) }
    composable(
        "detail/{itemId}",
        arguments = listOf(navArgument("itemId") { type = NavType.StringType })
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getString("itemId")
        DetailScreen(itemId = itemId ?: "")
    }
}

Type-Safe Navigation (Navigation 2.8+)

Navigation 2.8 introduced type-safe routes via Kotlin serialization — no more stringly-typed route strings.

@Serializable
data class Detail(val itemId: String)

NavHost(navController, startDestination = Home) {
    composable<Home> { HomeScreen(navController) }
    composable<Detail> { backStack ->
        val detail: Detail = backStack.toRoute()
        DetailScreen(detail.itemId)
    }
}

// Navigate
navController.navigate(Detail(itemId = "abc123"))

Deep Links

Navigation handles deep links with minimal setup. Declare your deep link URI in the composable destination and the system routes incoming intents automatically.

composable<Detail>(
    deepLinks = listOf(navDeepLink<Detail>(
        basePath = "https://example.com/item"
    ))
) { ... }

Bottom Navigation and NavGraph Nesting

Each tab in a bottom navigation should have its own nested NavGraph. This preserves back stacks per tab — users expect to return to the same position in each tab they left.

NavHost(navController, startDestination = "home_graph") {
    navigation(startDestination = "home", route = "home_graph") {
        composable("home") { HomeScreen(navController) }
        composable("home_detail") { HomeDetailScreen() }
    }
    navigation(startDestination = "profile", route = "profile_graph") {
        composable("profile") { ProfileScreen() }
    }
}

Back Stack Management

  • Use popUpTo with inclusive = true to clear the back stack when navigating to a new root (e.g., after login).
  • Use launchSingleTop = true to avoid duplicate destinations in the back stack from bottom nav taps.
  • Never navigate from background threads — always on the main thread.