
Navigation Compose
- 19 installs
- 60 repo stars
- Updated June 14, 2026
- ahmed3elshaer/everything-claude-code-mobile
navigation-compose is a Claude Code skill that provides Jetpack Compose Navigation patterns including type-safe routes, NavHost setup, argument passing, deep links, and nested graphs.
About
navigation-compose is a Claude Code skill that documents Jetpack Compose Navigation patterns. It covers type-safe routes with serializable sealed interfaces, NavHost configuration, argument and result passing, deep link registration, nested graphs, and bottom navigation. Developers use it when wiring navigation between Compose screens in an Android app. Both the type-safe and legacy navArgument approaches are shown.
- Type-safe Jetpack Compose Navigation with @Serializable sealed routes
- NavHost setup, argument passing, and result passing via SavedStateHandle
- Deep link registration for HTTPS and custom URI schemes
Navigation Compose by the numbers
- 19 all-time installs (skills.sh)
- Ranked #757 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
navigation-compose capabilities & compatibility
- Capabilities
- navigation · deep linking · routing
- Use cases
- frontend
What navigation-compose says it does
# Jetpack Compose Navigation Patterns
For non-serializable routes, use the classic approach:
npx skills add https://github.com/ahmed3elshaer/everything-claude-code-mobile --skill navigation-composeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 60 |
| Last updated | June 14, 2026 |
| Repository | ahmed3elshaer/everything-claude-code-mobile ↗ |
What it does
Wire Jetpack Compose Navigation with type-safe routes, argument and result passing, deep links, and nested graphs.
Who is it for?
Setting up navigation between Jetpack Compose screens with type-safe routes and deep links
Skip if: iOS/SwiftUI navigation or non-Compose Android navigation
When should I use this skill?
Adding navigation, deep links, or argument passing between Compose screens
What you get
Screens navigate via serializable sealed routes with typed arguments, results, and registered deep links
By the numbers
- Covers type-safe, legacy navArgument, result, and deep-link navigation approaches
Files
Jetpack Compose Navigation Patterns
Dependencies
dependencies {
implementation("androidx.navigation:navigation-compose:2.8.5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}Type-Safe Routes with Sealed Interface
@Serializable
sealed interface Route {
@Serializable
data object Home : Route
@Serializable
data object Settings : Route
@Serializable
data class UserProfile(val userId: String) : Route
@Serializable
data class PostDetail(val postId: Long, val showComments: Boolean = false) : Route
}
// For nested graphs
@Serializable
sealed interface AuthGraph {
@Serializable
data object Login : AuthGraph
@Serializable
data object Register : AuthGraph
@Serializable
data object ForgotPassword : AuthGraph
}NavHost Configuration
@Composable
fun AppNavHost(
navController: NavHostController = rememberNavController(),
modifier: Modifier = Modifier
) {
NavHost(
navController = navController,
startDestination = Route.Home,
modifier = modifier
) {
composable<Route.Home> {
HomeScreen(
onNavigateToProfile = { userId ->
navController.navigate(Route.UserProfile(userId))
},
onNavigateToSettings = {
navController.navigate(Route.Settings)
}
)
}
composable<Route.Settings> {
SettingsScreen(onBack = { navController.popBackStack() })
}
composable<Route.UserProfile> { backStackEntry ->
val route = backStackEntry.toRoute<Route.UserProfile>()
UserProfileScreen(userId = route.userId)
}
composable<Route.PostDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.PostDetail>()
PostDetailScreen(
postId = route.postId,
showComments = route.showComments
)
}
}
}Argument Passing with Legacy navArgument
For non-serializable routes, use the classic approach:
composable(
route = "post/{postId}?showComments={showComments}",
arguments = listOf(
navArgument("postId") { type = NavType.LongType },
navArgument("showComments") {
type = NavType.BoolType
defaultValue = false
}
)
) { backStackEntry ->
val postId = backStackEntry.arguments?.getLong("postId") ?: return@composable
val showComments = backStackEntry.arguments?.getBoolean("showComments") ?: false
PostDetailScreen(postId = postId, showComments = showComments)
}
// Navigate
navController.navigate("post/$postId?showComments=true")Navigation with Results (SavedStateHandle)
// Screen A: Navigate and listen for result
@Composable
fun ScreenA(navController: NavHostController) {
val result = navController.currentBackStackEntry
?.savedStateHandle
?.getStateFlow<String?>("selected_item", null)
?.collectAsState()
LaunchedEffect(result?.value) {
result?.value?.let { item ->
// Handle the result
}
}
Button(onClick = { navController.navigate(Route.ItemPicker) }) {
Text("Pick Item")
}
}
// Screen B: Set result and go back
@Composable
fun ItemPickerScreen(navController: NavHostController) {
Button(onClick = {
navController.previousBackStackEntry
?.savedStateHandle
?.set("selected_item", "chosen_value")
navController.popBackStack()
}) {
Text("Select This")
}
}Deep Link Registration
composable<Route.PostDetail>(
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/posts/{postId}"
},
navDeepLink {
uriPattern = "myapp://posts/{postId}"
}
)
) { backStackEntry ->
val route = backStackEntry.toRoute<Route.PostDetail>()
PostDetailScreen(postId = route.postId)
}AndroidManifest.xml intent filter:
<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" />
<data android:scheme="myapp" />
</intent-filter>
</activity>Nested Navigation Graphs
fun NavGraphBuilder.authNavGraph(navController: NavHostController) {
navigation<AuthGraph.Login>(startDestination = AuthGraph.Login) {
composable<AuthGraph.Login> {
LoginScreen(
onLoginSuccess = {
navController.navigate(Route.Home) {
popUpTo(AuthGraph.Login) { inclusive = true }
}
},
onNavigateToRegister = {
navController.navigate(AuthGraph.Register)
}
)
}
composable<AuthGraph.Register> {
RegisterScreen(onBack = { navController.popBackStack() })
}
composable<AuthGraph.ForgotPassword> {
ForgotPasswordScreen(onBack = { navController.popBackStack() })
}
}
}
// In the main NavHost
NavHost(navController = navController, startDestination = AuthGraph.Login) {
authNavGraph(navController)
composable<Route.Home> { HomeScreen() }
}Bottom Navigation
@Serializable
sealed interface BottomTab {
@Serializable data object Feed : BottomTab
@Serializable data object Search : BottomTab
@Serializable data object Profile : BottomTab
}
data class BottomNavItem(
val route: BottomTab,
val label: String,
val icon: ImageVector
)
val bottomNavItems = listOf(
BottomNavItem(BottomTab.Feed, "Feed", Icons.Default.Home),
BottomNavItem(BottomTab.Search, "Search", Icons.Default.Search),
BottomNavItem(BottomTab.Profile, "Profile", Icons.Default.Person)
)
@Composable
fun MainScreen() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
Scaffold(
bottomBar = {
NavigationBar {
bottomNavItems.forEach { item ->
val isSelected = navBackStackEntry?.destination?.hasRoute(
item.route::class
) == true
NavigationBarItem(
selected = isSelected,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
) { padding ->
NavHost(
navController = navController,
startDestination = BottomTab.Feed,
modifier = Modifier.padding(padding)
) {
composable<BottomTab.Feed> { FeedScreen() }
composable<BottomTab.Search> { SearchScreen() }
composable<BottomTab.Profile> { ProfileScreen() }
}
}
}Animated Transitions
composable<Route.PostDetail>(
enterTransition = {
slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Left,
animationSpec = tween(300)
)
},
exitTransition = {
slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Left,
animationSpec = tween(300)
)
},
popEnterTransition = {
slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Right,
animationSpec = tween(300)
)
},
popExitTransition = {
slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Right,
animationSpec = tween(300)
)
}
) { backStackEntry ->
val route = backStackEntry.toRoute<Route.PostDetail>()
PostDetailScreen(postId = route.postId)
}Navigation Testing
@Test
fun navigateToProfile_displaysUserProfile() {
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
composeTestRule.setContent {
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
composeTestRule.onNodeWithText("View Profile").performClick()
val currentRoute = navController.currentBackStackEntry?.destination?.route
assertTrue(currentRoute?.contains("UserProfile") == true)
}Best Practices
- Use type-safe routes with
@Serializabledata classes/objects over raw string routes. - Keep navigation logic out of composables; pass lambda callbacks (
onNavigateTo) instead. - Use
popUpTowithinclusive = truewhen navigating after login to clear the auth stack. - Use
launchSingleTop = truefor bottom tabs to prevent duplicate destinations. - Save and restore tab state with
saveState = trueandrestoreState = true. - Scope ViewModels to navigation entries with
hiltViewModel()orkoinViewModel(). - Test navigation by asserting on
navController.currentBackStackEntry.
Related skills
FAQ
How are type-safe routes defined?
As @Serializable sealed interfaces whose data objects and data classes become composable<Route.X> destinations.
How do you return a result to a previous screen?
By writing to the previous back stack entry's SavedStateHandle and calling popBackStack().