Navigation in Compose uses the navigation-compose library with a declarative NavHost pattern.
Add the dependency
dependencies {
implementation("androidx.navigation:navigation-compose:2.8.0")
}Basic navigation
// Define routes (type-safe with objects)
sealed class Screen(val route: String) {
object Home : Screen("home")
object Details : Screen("details/{itemId}") {
fun createRoute(itemId: String) = "details/$itemId"
}
object Settings : Screen("settings")
}
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) {
HomeScreen(
onNavigateToDetails = { itemId ->
navController.navigate(Screen.Details.createRoute(itemId))
},
onNavigateToSettings = {
navController.navigate(Screen.Settings.route)
}
)
}
composable(
route = Screen.Details.route,
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId") ?: ""
DetailsScreen(
itemId = itemId,
onBack = { navController.popBackStack() }
)
}
composable(Screen.Settings.route) {
SettingsScreen(onBack = { navController.popBackStack() })
}
}
}Type-safe navigation (Navigation 2.8+)
With Kotlin serialization, you get compile-time route safety:
// build.gradle.kts
plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
}
dependencies {
implementation("androidx.navigation:navigation-compose:2.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
}
// Define serializable routes
@Serializable object HomeRoute
@Serializable data class DetailsRoute(val itemId: String)
@Serializable object SettingsRoute
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = HomeRoute) {
composable<HomeRoute> {
HomeScreen(
onNavigateToDetails = { id -> navController.navigate(DetailsRoute(id)) }
)
}
composable<DetailsRoute> { backStackEntry ->
val route = backStackEntry.toRoute<DetailsRoute>()
DetailsScreen(itemId = route.itemId, onBack = { navController.popBackStack() })
}
composable<SettingsRoute> {
SettingsScreen(onBack = { navController.popBackStack() })
}
}
}Bottom navigation with Scaffold
@Composable
fun MainScreen() {
val navController = rememberNavController()
val currentBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry?.destination?.route
Scaffold(
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = currentRoute == "home",
onClick = { navController.navigate("home") { popUpTo("home") { inclusive = true } } },
icon = { Icon(Icons.Default.Home, contentDescription = "Home") },
label = { Text("Home") }
)
NavigationBarItem(
selected = currentRoute == "search",
onClick = { navController.navigate("search") },
icon = { Icon(Icons.Default.Search, contentDescription = "Search") },
label = { Text("Search") }
)
NavigationBarItem(
selected = currentRoute == "profile",
onClick = { navController.navigate("profile") },
icon = { Icon(Icons.Default.Person, contentDescription = "Profile") },
label = { Text("Profile") }
)
}
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = "home",
modifier = Modifier.padding(innerPadding)
) {
composable("home") { HomeScreen() }
composable("search") { SearchScreen() }
composable("profile") { ProfileScreen() }
}
}
}Passing data between screens
// Type-safe with data class
@Serializable data class DetailsRoute(val itemId: String, val from: String? = null)
// Navigate
navController.navigate(DetailsRoute(itemId = "42", from = "home"))
// Nested graphs
navigation<SettingsGraph>(startDestination = SettingsOverview) {
composable<SettingsOverview> { SettingsOverviewScreen() }
composable<SettingsDetail> { SettingsDetailScreen() }
}Deep links
composable(
route = "details/{itemId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/items/{itemId}"
action = Intent.ACTION_VIEW
}
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId") ?: ""
DetailsScreen(itemId = itemId)
}In your AndroidManifest.xml:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="example.com" />
</intent-filter>
</activity>