Skip to content

Intermediate: Android basics · Navigation and app architecture · topic 27 of 50 · 35 min

Navigation Compose

Explanation

Navigation Compose moves between screens inside one activity. A NavHost holds the screens, a NavController (from rememberNavController) moves between them, and each destination is a route. With type-safe routes, a route is a @Serializable object or data class, so arguments like an order id are checked by the compiler. navigate pushes a screen onto the back stack and popBackStack goes back.

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.

PieceRole
NavControllerMoves between destinations and keeps the back stack; create it with rememberNavController()
NavHostA composable that shows the current destination and lists all destinations (the nav graph)
RouteThe identity of a destination; with type-safe navigation, a @Serializable Kotlin type
Back stackThe 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.

kotlin
import kotlinx.serialization.Serializable

@Serializable object Home
@Serializable data class ProductDetails(val productId: Int)
@Serializable object Cart
An object for screens without arguments, a data class for screens that need data.
kotlin
@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() }
    }
}
toRoute() turns the back stack entry back into your typed route object.

The back stack

output
Start:            [Home]
Tap product 42:   [Home, ProductDetails(42)]
Add to cart:      [Home, ProductDetails(42), Cart]
Press Back:       [Home, ProductDetails(42)]
navigate pushes; Back or popBackStack pops.

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.
Think of it like this: The back stack is like a pile of visiting cards on your desk. Each new screen is a card put on top. Back takes the top card off. popUpTo is clearing several cards at once, like throwing away the login slip once you are inside the office.

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

NavHost + NavController

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.

kotlin
@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
    }
}
Back from OrderPlaced now goes straight to Home.

Practice (0/3 done)

Task 1 · Question

The back stack is [Home, Details(7)]. The user taps "Cart", which calls navigate(Cart), then presses Back twice. Where are they?

Task 2 · Question

What should you pass to a details screen through navigation?

Task 3 · Practice

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

Question 1

1.What does NavController do?

Question 2

2.Why are type-safe routes better than string routes?

Question 3

3.What does popBackStack() do?

Question 4

4.Why pass lambdas like onProductClick instead of the NavController to screens?

Read more (free)

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

Answer: setContent

setContent { } hosts the composables.

From Hello Compose: your first screen

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.

From Interview questions: Android and Compose

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.

From Routing with go_router

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

  1. 3 more topics in “Navigation and app architecture” before its exam: ViewModel and MVVM, App architecture: layers and repositories, Dependency injection with Hilt.
  2. Module 7 exam: Navigation and app architecture · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Saving data and calling APIs. 50 topics left in the course before the final exam.

← Previous: Text input and forms