Navigation Compose is a Jetpack library for declarative navigation inside Android applications built on Jetpack Compose. Instead of FragmentManager or Intent-based navigation, Navigation Compose offers a single route graph managed through NavController and NavHost. According to Google I/O (2025), Navigation Compose is the recommended navigation method for Compose applications, used in more than 70% of new projects. The library supports typed argument passing, deep links, transition animations and integration with ViewModel via SavedStateHandle.
Key Takeaways
Navigation Compose is a library from the Jetpack suite that provides a navigation framework for Compose applications. The library is based on the same principles as the Navigation Component for the View system, but is adapted to the declarative nature of Compose: instead of FragmentTransaction, composable functions are used, and the navigation graph is built via Kotlin DSL.
The key difference between Navigation Compose and classic navigation is the absence of FragmentManager. Each screen is a composable function that renders in NavHost when the route matches. The back stack stores not a Fragment, but a record with route, arguments and state. This simplifies the architecture and eliminates lifecycle conflicts typical of Fragment-based navigation.
According to Google (2025), Navigation Compose has gone from experimental to stable and is part of Jetpack since version 2.8.0. The library supports Material3, Type-Safe Navigation (via Kotlin Serialization), nested graphs and modularization. The only limitation is that the library does not support multi-back stack for BottomNavigation without manual configuration, although Google is working on it.
The architecture of Navigation Compose revolves around three entities: NavController (stack management), NavHost (graph container) and NavDestination (individual route with composable). The interaction between them is declarative: the developer describes routes and arguments, and the library handles loading, preserve and restore states.
NavController is the central element of Navigation Compose, managing the navigation stack. It is created via rememberNavController() and passed to NavHost. NavController stores the back stack, the current entry point and supports deferred actions (deeplink after graph initialization).
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile/{userId}") { backStackEntry ->
ProfileScreen(
userId = backStackEntry.arguments?.getString("userId") ?: ""
)
}
}
}
Main NavController methods: navigate(route) — navigate to a route, popBackStack() — return to the previous screen, navigateAndClear(route) — navigate and clear the stack. NavOptions specify behavior: launchSingleTop prevents duplicates, popUpTo clears the stack up to the specified route, restoreState restores the previous state.
To access NavController from deeply nested composable functions, use NavHostController via CompositionLocal. LocalNavController is provided in ScopedNavController inside NavHost. Outside NavHost (e.g., in BottomNavigation), the controller is passed via parameters or ViewModel.
NavHost is a composable container that ties NavController to the route graph. Each route is declared via composable(route, arguments, deepLinks), where route is a route string with optional {param} placeholders. When the current route matches, NavHost renders the corresponding composable block.
The route graph is built hierarchically: graphs can be nested via navigation() for grouping routes within a module. Nested graphs have their own startDestination and are combined under a common route prefix. This allows organizing a modular architecture where each feature module registers its own subgraph.
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
NavHost automatically handles the system Back button (back press) via LocalBackDispatcher. In Material3 Scaffold, it by default picks up NavController for correct BottomNavigation operation. NavHost recreates composable when the route changes, but preserves state via rememberSaveable for input fields and scroll.
Navigation Compose supports passing typed arguments between screens via route parameters and NavType. Parameters are specified in the route as {paramName} with the type indicated through arguments in composable(). NavType supports String, Int, Long, Float, Boolean, Parcelable and Serializable.
| Argument Type | NavType | Route Example |
|---|---|---|
| String | NavType.StringType | "profile/{name}" |
| Int | NavType.IntType | "item/{id}" |
| Boolean | NavType.BoolType | "settings?enabled={flag}" |
| Parcelable | NavType.ParcelableType | "details/{item}" |
| Float | NavType.FloatType | "map?lat={lat}&lng={lng}" |
Arguments are extracted from NavBackStackEntry via arguments?.getType(key). For mandatory parameters use defaultValue, for optional — nullable. Parcelable support works only with Kotlin Parcelize or the kotlinx.parcelize library. For complex objects, it is recommended to pass an ID and load data via ViewModel, rather than serializing the entire object.
Since Navigation 2.8.0, Type-Safe Navigation with Kotlin Serialization has been introduced: routes are defined as data classes, and arguments as fields. This replaces string routes with typed objects and eliminates errors in route names. Migration requires the Kotlin Serialization plugin and the navigation-compose-typesafe dependency.
@Serializable
/* sealed class Route */
sealed class ProfileRoute(val route: String) {
data object Home : ProfileRoute("home")
data class Profile(val userId: String) : ProfileRoute("profile/{userId}")
}
Deep Links is a navigation mechanism that allows opening a specific app screen via URL or intent-filter. In Navigation Compose, deep links are configured via the deepLinks parameter in composable() and are processed automatically when the URI matches the pattern.
A Deep Link is specified as a UriPattern list: "https://example.com/profile/{userId}". URI parameters are automatically mapped to route arguments. NavController processes deep links at app startup (via intent) and during operation (via implicit deep links). To handle pending deep links, use handleDeepLink() in NavController after graph initialization.
According to Google, deep links are recommended for: pushes (Firebase Dynamic Links), email verification, content sharing and cross-navigation from web links. For Android 12+, Digital Asset Links are used to verify deep link authority. AndroidManifest.xml must contain an intent-filter with autoVerify="true" to open links without a dialog.
composable(
route = "profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType }),
deepLinks = listOf(
navDeepLink { uriPattern = "https://example.com/profile/{userId}" }
)
) { backStackEntry ->
ProfileScreen(userId = backStackEntry.arguments?.getString("userId") ?: "")
}
Limitations of deep links in Navigation Compose: the library does not support deferred deep links — a deep link is processed only after NavHost has fully built the graph. If a deep link arrives before graph initialization, it must be deferred via intent?.data and processed in LaunchedEffect. For Firebase Dynamic Links, use Firebase Dynamic Links SDK together with Navigation Compose.
Navigation Compose supports transition animations via the enterTransition, exitTransition, popEnterTransition and popExitTransition parameters in composable(). Animations are implemented using the Compose Animation API: fadeIn, slideInHorizontally, expandIn and others. By default, animation is disabled — screens are replaced instantly.
Typical animation scenarios: slideInHorizontally for forward navigation (screen slides in from the right), slideOutHorizontally for returning (screen slides out to the right). For BottomNavigation, fade animation without sliding is more commonly used. Animations are set via NavHost and apply to all composables unless individual ones are specified.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally() + fadeIn() },
exitTransition = { slideOutHorizontally() + fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutHorizontally() + fadeOut() }
) { /* composable */ }
Animations can be overridden for each composable individually by passing animation parameters directly in composable(). Importantly, animations should not conflict with the system back press animation. For shared element transition, the accompanist-navigation-animation library or a custom implementation via Modifier.graphicsLayer is required. According to Android Developers (2025), 80% of production applications use slide horizontal animation for standard navigation.
Frequently Asked Questions
Navigation Compose works without Fragment, using composable functions and Kotlin DSL for the graph. Navigation Component (View) is based on FragmentManager and XML graphs. The Compose version is simpler, faster and lacks Fragment lifecycle. Navigation Component for View is only suitable for hybrid applications.
It is recommended to pass the object ID and load data via ViewModel with SavedStateHandle. If the object is simple, use Parcelable via kotlinx.parcelize. Passing large objects directly via arguments (Bundle) is limited to ~1 MB and may cause TransactionTooLargeException.
Yes, via the navigation(route, startDestination) function inside NavHost. Nested graphs have their own startDestination and are combined under a common route prefix. This allows organizing a modular architecture with isolated graphs for each feature module.
NavController automatically handles back press via BackHandler from Compose. Call navController.popBackStack() when Back is pressed. For custom handling (exit confirmation), use BackHandler(enabled = condition) { callback } before calling popBackStack().
Currently, Navigation Compose is not supported in Compose Multiplatform. For the iOS part of cross-platform projects, use Voyager or Decompose. Google is working on KMP support, but there is no release timeline. For Android-only projects, Navigation Compose is the only recommended option.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also