Intermediate: Android basics · Navigation and app architecture · topic 27 of 50 · 35 min
Navigation Compose
Explanation
Why a navigation library?
Real apps have many screens: home, product details, cart, checkout, profile. You need to move between them, pass data (which product?), and make the system Back button behave correctly. Doing this by hand is error-prone. Navigation Compose, part of Android Jetpack, manages it for you inside a single activity.
| Piece | Role |
|---|---|
| NavController | Moves between destinations and keeps the back stack; create it with rememberNavController() |
| NavHost | A composable that shows the current destination and lists all destinations (the nav graph) |
| Route | The identity of a destination; with type-safe navigation, a @Serializable Kotlin type |
| Back stack | The list of screens the user has visited; Back removes the top one |
Type-safe routes
Older Navigation code used string routes like "details/{id}", where a typo or a wrong argument type crashed at run time. Since Navigation 2.8, routes can be Kotlin types marked @Serializable (from the kotlinx.serialization plugin). The compiler then checks that you pass the right arguments.
import kotlinx.serialization.Serializable
@Serializable object Home
@Serializable data class ProductDetails(val productId: Int)
@Serializable object Cart@Composable
fun ShopApp() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Home) {
composable<Home> {
HomeScreen(onProductClick = { id -> navController.navigate(ProductDetails(id)) })
}
composable<ProductDetails> { backStackEntry ->
val route: ProductDetails = backStackEntry.toRoute()
DetailsScreen(
productId = route.productId,
onAddedToCart = { navController.navigate(Cart) },
onBack = { navController.popBackStack() },
)
}
composable<Cart> { CartScreen() }
}
}The back stack
Start: [Home]
Tap product 42: [Home, ProductDetails(42)]
Add to cart: [Home, ProductDetails(42), Cart]
Press Back: [Home, ProductDetails(42)]Sometimes you do not want a screen to stay in the back stack. After login, the user should not go "back" to the login screen. Use navOptions: navController.navigate(Home) { popUpTo<Login> { inclusive = true } } removes Login before showing Home.
Good habits
- Pass only small IDs as arguments (productId), not whole objects. The next screen loads the details from the repository. This survives process death and keeps routes simple.
- Do not pass the NavController deep into your composables. Pass lambdas like onProductClick, so screens stay reusable and testable.
- Bottom navigation (a NavigationBar with Home, Search, Orders) uses the same NavHost; each tab navigates to a top-level destination.
Navigation 3
Google has also released Navigation 3, a newer library designed around Compose where you own the back stack as a list in your state. It is a good option for new projects; the ideas above (destinations, back stack, passing IDs) still apply. Check the current Android documentation to decide which library your project should use.
Common mistakes
- Forgetting the kotlinx.serialization plugin and @Serializable on route types: a run-time or build error.
- Creating a new NavController inside a screen instead of using the one from rememberNavController at the top.
- Passing large objects through navigation instead of an ID.
- Leaving the login screen in the back stack after sign-in.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Navigation Compose · 1/6
Navigation Compose moves between screens inside one activity and handles the Back button.
Prefer a video by a teacher? Find free videos on “navigation compose type safe routes serializable navhost tutorial” on YouTube ↗
In real life: An e-commerce flow
A clothing app has Home, ProductDetails(productId), Cart and Checkout. Tapping a product navigates with its id; the details screen loads the product from the repository. After payment succeeds, the app navigates to an OrderPlaced(orderId) screen and pops Cart and Checkout off the back stack, so pressing Back returns to Home instead of to a payment page.
@Serializable data class OrderPlaced(val orderId: String)
fun onPaymentSuccess(navController: NavController, orderId: String) {
navController.navigate(OrderPlaced(orderId)) {
popUpTo<Home>() // keep Home, remove everything above it
}
}Practice (0/3 done)
The back stack is [Home, Details(7)]. The user taps "Cart", which calls navigate(Cart), then presses Back twice. Where are they?
What should you pass to a details screen through navigation?
Build a 3-screen app with type-safe routes: Login, Home and Profile(userName: String). Login navigates to Home and removes Login from the back stack; Home has a button to open Profile with a name; Profile shows the name and has a Back button using popBackStack. Check the system Back button behaviour.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- ViewModel and MVVMNavigation and app architecture · same module
- App architecture: layers and repositoriesNavigation and app architecture · same module
- Dependency injection with HiltNavigation and app architecture · same module
- Navigator basicsFlutter and Dart · Lists, navigation and forms
- Interview questions: Android and ComposeAndroid career path
- Routing with go_routerFlutter and Dart · Lists, navigation and forms
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does NavController do?
Answer: Moves between destinations and keeps the back stack
It is the navigator.
Why are type-safe routes better than string routes?
Answer: The compiler checks destinations and argument types
Typos and wrong argument types become compile errors.
What does popBackStack() do?
Answer: Removes the top destination and shows the previous one
Same as pressing Back.
Why pass lambdas like onProductClick instead of the NavController to screens?
Answer: Screens stay reusable, previewable and testable
Screens should not depend on navigation details.
Which is the recommended way to pass a product id to a details screen with Navigator.push?Related
Answer: The details screen's constructor
Constructor arguments are explicit and type-safe.
From Navigator basics
Which function shows Compose content in an activity?Related
Why must a ViewModel not hold an Activity reference?Related
Answer: The ViewModel outlives the activity, causing a memory leak
The activity is recreated on rotation.
Why use a router package like go_router?Related
Answer: For URL-based routes, deep links and central redirects
Especially for web and larger apps.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Navigation and app architecture” before its exam: ViewModel and MVVM, App architecture: layers and repositories, Dependency injection with Hilt.
- Module 7 exam: Navigation and app architecture · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Saving data and calling APIs. 50 topics left in the course before the final exam.