Navigation in Compose
One activity, many composable screens
Compose apps usually have a single Activity and navigate between composable “destinations” using Navigation-Compose. You define a NavHost listing your screens, and a NavController moves between them.
Setting up
@Composable
fun AppNav() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(onOpenProfile = { id ->
navController.navigate("profile/$id")
})
}
composable("profile/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")
ProfileScreen(userId = id)
}
}
}
Passing arguments properly
composable(
"profile/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { entry ->
val id = entry.arguments?.getInt("id") ?: 0
ProfileScreen(id)
}
Going back
navController.popBackStack() // go back one screen
navController.navigate("home") {
popUpTo("home") { inclusive = true } // clear the back stack
}
Keep navigation out of deep composables
Don’t pass the NavController deep into your UI. Instead, expose events (onOpenProfile: (Int) -> Unit) and handle navigation at the NavHost level. This keeps screens reusable and testable.
Bottom navigation
Combine a Scaffold with a NavigationBar for the common bottom-tab layout, each tab navigating to a destination while preserving its state.
Common mistakes
- Passing the
NavControllereverywhere instead of hoisting navigation events. - Forgetting to declare argument types, leading to crashes parsing them.
- Not using
popUpToon login → home, leaving the login screen on the back stack.
Summary: Define screens in aNavHost, move with aNavController, declare typed arguments, and hoist navigation events instead of passing the controller down.