Step 6 of 15
NavHost, composable destinations, passing arguments, bottom navigation
Navigation ใน Compose — NavHost, routes, arguments, bottom nav
The Navigation Compose library manages screens and the back stack. You define routes in a NavHost, and each route maps to a composable.
// build.gradle.kts
// implementation("androidx.navigation:navigation-compose:2.9.0")
import androidx.navigation.compose.*
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("detail") { DetailScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
rememberNavController() — holds and controls the back stackNavHost — the container that shows the current destinationcomposable("home") — registers a routeCall navController.navigate("route") to move forward. Call navController.popBackStack() to go back.
@Composable
fun HomeScreen(navController: NavController) {
Column {
Text("Home")
Button(onClick = { navController.navigate("detail") }) {
Text("Go to Detail")
}
}
}
Add a placeholder to the route, then declare its type:
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable(
route = "detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(itemId = itemId, navController = navController)
}
}
Navigate with the value in the path:
navController.navigate("detail/42") // itemId becomes 42
Combine Scaffold with a NavigationBar to switch between top-level destinations.
Diagram: A three-tab app with Home, Search, and Profile.
Loading diagram...
Step 1 — define the tabs:
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
data class TabItem(val route: String, val label: String, val icon: androidx.compose.ui.graphics.vector.ImageVector)
val tabs = listOf(
TabItem("home", "Home", Icons.Filled.Home),
TabItem("search", "Search", Icons.Filled.Search),
TabItem("profile", "Profile", Icons.Filled.Person),
)
Step 2 — wire up Scaffold and NavHost:
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
NavigationBar {
tabs.forEach { tab ->
NavigationBarItem(
selected = currentRoute == tab.route,
onClick = {
navController.navigate(tab.route) {
popUpTo("home") { saveState = true }
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(tab.icon, contentDescription = tab.label) },
label = { Text(tab.label) }
)
}
}
}
) { padding ->
NavHost(navController, startDestination = "home", Modifier.padding(padding)) {
composable("home") { Text("Home", Modifier.padding(16.dp)) }
composable("search") { Text("Search", Modifier.padding(16.dp)) }
composable("profile") { Text("Profile", Modifier.padding(16.dp)) }
}
}
}
The navigation options keep state when switching tabs and avoid stacking duplicate screens:
popUpTo("home") { saveState = true } — pop back to the start, save statelaunchSingleTop = true — do not create the same screen twicerestoreState = true — restore the tab's previous stateGroup related routes into a graph. This is useful for a login flow or a checkout flow that you want to clear together:
navigation(startDestination = "login", route = "auth") {
composable("login") { LoginScreen(navController) }
composable("register") { RegisterScreen(navController) }
}
Navigate to the whole graph with navController.navigate("auth").
Build a 3-screen app: Home, Detail (takes an Int argument), and Settings. Add a bottom bar with two tabs. From Home, a button navigates to Detail with id 7. The Detail screen shows "Item #7" and has a Back button.