Navigation in Compose

Jetpack Compose Mid-level Kotlin 2.0+ API 23+ Reviewed By Best Latest Apps

Pick the fix

The back stack is Home → Cart → Checkout. After a successful checkout, Back should go to Home, not back into Checkout or Cart. Which navigate call is right?

Options
Hint 1 of 1

What should remain under Done?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A navigate(Done) { popUpTo<Home>() }

Correct. Everything above Home is popped, so Back from Done returns to Home.

Why not the others?
  • B navigate(Done). Back would return to Checkout.
  • C navigate(Done) { launchSingleTop = true }. That prevents duplicates of Done, not Checkout staying behind.
  • D popBackStack(); navigate(Done). Only Checkout is popped; Cart is still between Home and Done.

30-second answer

Navigation Compose keeps a back stack of destinations in a NavController. Since Navigation 2.8, routes can be @Serializable classes or objects, so arguments are typed: navController.navigate(Profile(id = "42")) and backStackEntry.toRoute<Profile>(). Pass IDs, not whole objects, and load data in the destination's ViewModel.

Practise saying it · 40 s. No recording, just a timer

Explain Navigation Compose and arguments.

Say your answer out loud, then check which of these points you covered:

  • NavController keeps a back stack
  • Type-safe @Serializable routes
  • Pass IDs, load in the ViewModel
  • popUpTo and launchSingleTop
  • Per-entry ViewModels

Deep dive

Each back-stack entry has its own ViewModelStore and SavedStateHandle, so a destination's ViewModel lives as long as its entry.

popUpTo(Home) { inclusive = false } clears entries above Home; launchSingleTop avoids duplicates on repeated taps.

Bottom navigation uses saveState = true and restoreState = true to keep each tab's stack.

Navigation 3 is a newer library where you own the back stack as a list; know it exists, and that Navigation 2.x is still the common one.

Type-safe routes Compile-checked · Kotlin 2.4.21
@Serializable object Home
@Serializable data class Profile(val id: String)

@Composable
fun AppNav(navController: NavHostController) {
    NavHost(navController, startDestination = Home) {
        composable<Home> {
            Button(onClick = { navController.navigate(Profile("42")) }) { Text("Open") }
        }
        composable<Profile> { entry ->
            val route = entry.toRoute<Profile>()
            Text("Profile ${route.id}")
        }
    }
}

Common mistakes

  • Passing large objects or Parcelables as arguments.
  • Calling navigate in a composable body, so it fires on every recomposition.
  • Forgetting launchSingleTop and stacking the same screen twice.

Senior take & follow-ups

Mention type-safe routes and per-entry ViewModels; that shows current knowledge.

Follow-up questions

Think of your answer first, then open the hint.

  1. Where should the navigate call go?

    Show hint

    In an event callback, often passed down as a lambda.

  2. How does the destination ViewModel read the argument?

    Show hint

    savedStateHandle.toRoute<Profile>().

Similar on iOS: NavigationStack and deep links SwiftUI NavigationStack holds a path you append to; Navigation Compose keeps entries in a NavController.

Free tool

Deep link tester

Free tool: build the adb and intent:// commands for any link and check an intent filter before you ship it.

Suggest a fix if something here is wrong or out of date.