{"id":"34c55978-d0ab-45ac-ad34-a99ce8ec4e5a","shortId":"aEy85F","kind":"skill","title":"compose-navigation","tagline":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","description":"# Compose Navigation\r\n\r\nType-safe navigation prevents the most common navigation bugs in AI-built apps.\r\nThese rules cover the complete navigation pattern from setup to ---\r\nname: compose-navigation\r\ndescription: |\r\n  Jetpack Compose Navigation for Android AI agents — complete patterns. Use this skill for\r\n  any navigation: NavHost, NavController, type-safe @Serializable routes, back stack,\r\n  deep links, navigation arguments, composable(), nested graphs, shared ViewModels,\r\n  navigateUp, navigateTopLevel, launchSingleTop, popUpTo, shared element transitions,\r\n  predictive back gesture, transition animations between screens, bottom navigation,\r\n  navigation with Hilt, passing arguments, result passing via SavedStateHandle,\r\n  or any navigation pattern in Compose. Always apply before writing any NavHost or route.\r\n---\r\n\r\n# Compose Navigation — Complete Guide\r\n\r\nType-safe navigation with shared element transitions and predictive back.\r\nThese rules cover every pattern from simple routes to complex multi-graph apps.\r\n\r\n## Setup\r\n\r\n```toml\r\n[versions]\r\nnavigationCompose = \"2.8.5\"\r\n[libraries]\r\nnavigation-compose = { group = \"androidx.navigation\", name = \"navigation-compose\", version.ref = \"navigationCompose\" }\r\n```\r\n\r\n## Rule 1: All routes are @Serializable — zero string routes\r\n\r\n```kotlin\r\n// navigation/Routes.kt — ALL routes defined here, nowhere else\r\n\r\n@Serializable object HomeRoute\r\n@Serializable object SearchRoute\r\n@Serializable object InboxRoute\r\n@Serializable object ProfileRoute\r\n@Serializable object SettingsRoute\r\n\r\n// Routes with required arguments\r\n@Serializable data class ItemDetailRoute(val itemId: String)\r\n@Serializable data class EditItemRoute(val itemId: String, val isNew: Boolean = false)\r\n@Serializable data class UserProfileRoute(val userId: String, val fromNotification: Boolean = false)\r\n\r\n// Graph containers\r\n@Serializable object AuthGraph\r\n@Serializable object LoginRoute\r\n@Serializable object SignUpRoute\r\n@Serializable object ForgotPasswordRoute\r\n\r\n@Serializable object OnboardingGraph\r\n@Serializable object OnboardingStep1Route\r\n@Serializable object OnboardingStep2Route\r\n@Serializable object OnboardingStep3Route\r\n\r\n// ❌ NEVER use string routes\r\nnavController.navigate(\"detail/$itemId\")           // typo-prone, no type safety\r\ncomposable(\"detail/{itemId}\") { backStackEntry ->\r\n    backStackEntry.arguments?.getString(\"itemId\")  // nullable, untyped\r\n}\r\n```\r\n\r\n## Rule 2: Complete NavHost — every destination registered\r\n\r\n```kotlin\r\n// navigation/AppNavHost.kt\r\n@Composable\r\nfun AppNavHost(\r\n    navController: NavHostController = rememberNavController(),\r\n    startDestination: Any = if (isLoggedIn) HomeRoute else AuthGraph\r\n) {\r\n    NavHost(\r\n        navController    = navController,\r\n        startDestination = startDestination,\r\n        // M3 Expressive enter/exit transitions (applied to ALL screens by default)\r\n        enterTransition  = { slideInHorizontally(\r\n            initialOffsetX = { it },\r\n            animationSpec  = tween(Duration.medium4, easing = AppEasing.EmphasizedDecel)\r\n        ) + fadeIn(tween(Duration.medium4)) },\r\n        exitTransition   = { slideOutHorizontally(\r\n            targetOffsetX = { -it / 4 },\r\n            animationSpec = tween(Duration.short4, easing = AppEasing.EmphasizedAccel)\r\n        ) + fadeOut(tween(Duration.short4)) },\r\n        popEnterTransition = { slideInHorizontally(\r\n            initialOffsetX = { -it / 4 },\r\n            animationSpec  = tween(Duration.medium4, easing = AppEasing.EmphasizedDecel)\r\n        ) + fadeIn(tween(Duration.medium4)) },\r\n        popExitTransition  = { slideOutHorizontally(\r\n            targetOffsetX = { it },\r\n            animationSpec = tween(Duration.short4, easing = AppEasing.EmphasizedAccel)\r\n        ) + fadeOut(tween(Duration.short4)) }\r\n    ) {\r\n        // Top-level destinations\r\n        composable<HomeRoute>   { HomeScreen(navController = navController) }\r\n        composable<SearchRoute> { SearchScreen(navController = navController) }\r\n        composable<InboxRoute>  { InboxScreen(navController = navController) }\r\n        composable<ProfileRoute>{ ProfileScreen(navController = navController) }\r\n        composable<SettingsRoute>{ SettingsScreen(onBack = { navController.navigateUp() }) }\r\n\r\n        // Detail screens\r\n        composable<ItemDetailRoute> { entry ->\r\n            val route: ItemDetailRoute = entry.toRoute()\r\n            ItemDetailScreen(\r\n                itemId  = route.itemId,\r\n                onEdit  = { navController.navigate(EditItemRoute(route.itemId)) },\r\n                onBack  = { navController.navigateUp() }\r\n            )\r\n        }\r\n        composable<EditItemRoute> { entry ->\r\n            val route: EditItemRoute = entry.toRoute()\r\n            EditItemScreen(\r\n                itemId = route.itemId,\r\n                isNew  = route.isNew,\r\n                onDone = { navController.navigateUp() }\r\n            )\r\n        }\r\n\r\n        // Auth flow — nested graph\r\n        navigation<AuthGraph>(startDestination = LoginRoute) {\r\n            composable<LoginRoute> {\r\n                LoginScreen(\r\n                    onLoginSuccess = {\r\n                        navController.navigate(HomeRoute) {\r\n                            popUpTo(AuthGraph) { inclusive = true }  // clear auth stack entirely\r\n                        }\r\n                    },\r\n                    onSignUp       = { navController.navigate(SignUpRoute) },\r\n                    onForgotPass   = { navController.navigate(ForgotPasswordRoute) }\r\n                )\r\n            }\r\n            composable<SignUpRoute> {\r\n                SignUpScreen(\r\n                    onSuccess = { navController.navigate(HomeRoute) { popUpTo(AuthGraph) { inclusive = true } } },\r\n                    onBack    = { navController.navigateUp() }\r\n                )\r\n            }\r\n            composable<ForgotPasswordRoute> {\r\n                ForgotPasswordScreen(onBack = { navController.navigateUp() })\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Rule 3: Top-level navigation — launchSingleTop prevents duplicate entries\r\n\r\n```kotlin\r\n// ✅ Navigation helper for tab/nav items — prevents backstack pollution\r\nfun NavController.navigateTopLevel(route: Any) {\r\n    navigate(route) {\r\n        // Pop to start destination, saving state for restoration\r\n        popUpTo(graph.findStartDestination().id) {\r\n            saveState = true\r\n        }\r\n        launchSingleTop = true  // don't create duplicate on re-tap\r\n        restoreState = true     // restore state when re-navigating to tab\r\n    }\r\n}\r\n\r\n// Usage in NavigationSuiteScaffold\r\nitem(\r\n    selected = currentDestination?.hasRoute<HomeRoute>() == true,\r\n    onClick  = { navController.navigateTopLevel(HomeRoute) }\r\n)\r\n```\r\n\r\n## Rule 4: Shared Element Transitions — M3 Expressive\r\n\r\n```kotlin\r\n// ✅ Shared element transition between list item and detail screen\r\n// Navigation 2.8+ with material3 1.4+ supports SharedTransitionLayout\r\n\r\n@Composable\r\nfun HomeScreen(navController: NavController) {\r\n    SharedTransitionLayout {\r\n        AnimatedContent(targetState = selectedItem, label = \"shared-transition\") { item ->\r\n            if (item == null) {\r\n                LazyColumn {\r\n                    items(items, key = { it.id }) { item ->\r\n                        ItemCard(\r\n                            item = item,\r\n                            animatedVisibilityScope = this@AnimatedContent,\r\n                            sharedTransitionScope = this@SharedTransitionLayout,\r\n                            onClick = { selectedItem = item }\r\n                        )\r\n                    }\r\n                }\r\n            } else {\r\n                ItemDetailScreen(\r\n                    item = item,\r\n                    animatedVisibilityScope = this@AnimatedContent,\r\n                    sharedTransitionScope = this@SharedTransitionLayout,\r\n                    onBack = { selectedItem = null }\r\n                )\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n// ✅ In list item — mark shared elements\r\n@Composable\r\nfun ItemCard(\r\n    item: Item,\r\n    sharedTransitionScope: SharedTransitionScope,\r\n    animatedVisibilityScope: AnimatedVisibilityScope,\r\n    onClick: () -> Unit,\r\n) {\r\n    with(sharedTransitionScope) {\r\n        Card(onClick = onClick) {\r\n            AsyncImage(\r\n                model = item.imageUrl,\r\n                contentDescription = null,\r\n                modifier = Modifier\r\n                    .sharedElement(\r\n                        state = rememberSharedContentState(\"image-${item.id}\"),\r\n                        animatedVisibilityScope = animatedVisibilityScope\r\n                    )\r\n                    .fillMaxWidth().aspectRatio(16f / 9f)\r\n            )\r\n            Text(\r\n                item.title,\r\n                modifier = Modifier.sharedElement(\r\n                    state = rememberSharedContentState(\"title-${item.id}\"),\r\n                    animatedVisibilityScope = animatedVisibilityScope\r\n                )\r\n            )\r\n        }\r\n    }\r\n}\r\n\r\n// ✅ In detail screen — same shared element keys\r\n@Composable\r\nfun ItemDetailScreen(\r\n    item: Item,\r\n    sharedTransitionScope: SharedTransitionScope,\r\n    animatedVisibilityScope: AnimatedVisibilityScope,\r\n    onBack: () -> Unit\r\n) {\r\n    with(sharedTransitionScope) {\r\n        Column {\r\n            AsyncImage(\r\n                model = item.imageUrl,\r\n                contentDescription = item.title,\r\n                modifier = Modifier\r\n                    .sharedElement(\r\n                        state = rememberSharedContentState(\"image-${item.id}\"),\r\n                        animatedVisibilityScope = animatedVisibilityScope\r\n                    )\r\n                    .fillMaxWidth().aspectRatio(16f / 9f)\r\n            )\r\n            Text(\r\n                item.title,\r\n                style = MaterialTheme.typography.headlineMedium,\r\n                modifier = Modifier.sharedElement(\r\n                    state = rememberSharedContentState(\"title-${item.id}\"),\r\n                    animatedVisibilityScope = animatedVisibilityScope\r\n                ).padding(Spacing.md)\r\n            )\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Rule 5: Deep links with type-safe routes\r\n\r\n```kotlin\r\n// ✅ Deep link registered on destination\r\ncomposable<ItemDetailRoute>(\r\n    deepLinks = listOf(\r\n        navDeepLink<ItemDetailRoute>(basePath = \"https://myapp.com/items\")\r\n        // matches: https://myapp.com/items/{itemId}\r\n    )\r\n) { entry ->\r\n    val route: ItemDetailRoute = entry.toRoute()\r\n    ItemDetailScreen(itemId = route.itemId)\r\n}\r\n\r\n// AndroidManifest.xml — intent filter\r\n// <activity android:name=\".MainActivity\">\r\n//     <intent-filter android:autoVerify=\"true\">\r\n//         <action android:name=\"android.intent.action.VIEW\"/>\r\n//         <category android:name=\"android.intent.category.DEFAULT\"/>\r\n//         <category android:name=\"android.intent.category.BROWSABLE\"/>\r\n//         <data android:scheme=\"https\" android:host=\"myapp.com\"/>\r\n//     </intent-filter>\r\n// </activity>\r\n```\r\n\r\n## Rule 6: Shared ViewModel scoped to NavGraph\r\n\r\n```kotlin\r\n// ✅ Share state across multiple screens in a graph\r\nnavigation<CheckoutGraph>(startDestination = CartRoute) {\r\n    composable<CartRoute> { entry ->\r\n        val parentEntry = remember(entry) { navController.getBackStackEntry(CheckoutGraph) }\r\n        val viewModel: CheckoutViewModel = hiltViewModel(parentEntry)\r\n        CartScreen(viewModel)\r\n    }\r\n    composable<ShippingRoute> { entry ->\r\n        val parentEntry = remember(entry) { navController.getBackStackEntry(CheckoutGraph) }\r\n        val viewModel: CheckoutViewModel = hiltViewModel(parentEntry)\r\n        ShippingScreen(viewModel)\r\n    }\r\n    composable<PaymentRoute> { entry ->\r\n        val parentEntry = remember(entry) { navController.getBackStackEntry(CheckoutGraph) }\r\n        val viewModel: CheckoutViewModel = hiltViewModel(parentEntry)\r\n        PaymentScreen(viewModel)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 7: Result passing via SavedStateHandle\r\n\r\n```kotlin\r\n// ✅ Pass result back to previous destination\r\n// In destination that produces result:\r\n@Composable\r\nfun CreateItemScreen(navController: NavController) {\r\n    val onSaved = { newItemId: String ->\r\n        navController.previousBackStackEntry\r\n            ?.savedStateHandle\r\n            ?.set(\"new_item_id\", newItemId)\r\n        navController.navigateUp()\r\n    }\r\n}\r\n\r\n// In destination that receives result:\r\n@HiltViewModel\r\nclass HomeViewModel @Inject constructor(\r\n    savedStateHandle: SavedStateHandle\r\n) : ViewModel() {\r\n    val newItemId = savedStateHandle.getStateFlow<String?>(\"new_item_id\", null)\r\n}\r\n```\r\n\r\n## Rule 8: Predictive back — enable in manifest\r\n\r\n```xml\r\n<!-- AndroidManifest.xml — required for predictive back gesture (Android 14+) -->\r\n<application android:enableOnBackInvokedCallback=\"true\">\r\n```\r\n\r\n```kotlin\r\n// ✅ NavHost 2.8+ handles predictive back automatically for type-safe routes\r\n// ✅ Custom back handler with animation for non-NavHost back\r\nBackHandler {\r\n    coroutineScope.launch {\r\n        // Animate out before popping\r\n        isVisible = false\r\n        delay(Duration.short4.toLong())\r\n        navController.navigateUp()\r\n    }\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ String routes anywhere — use `@Serializable` object/data class\r\n❌ `navController.navigate(route)` for tabs — use `navigateTopLevel()`\r\n❌ `navController.navigate(SomeRoute) { popUpTo(0) }` — pops past graph root; use `graph.findStartDestination().id`\r\n❌ `hiltViewModel()` in every screen that shares state — scope to NavGraph entry\r\n❌ Missing `popUpTo(AuthGraph) { inclusive = true }` after login — user can press Back to login\r\n❌ Passing complex objects as route args — pass only IDs, fetch in ViewModel\r\n❌ SharedElement keys not matching between list and detail — transition won't animate\r\n❌ No screen-level transitions in NavHost — add enter/exit/pop specs at NavHost leveldeep links.\r\n\r\n## Setup\r\n\r\n```toml\r\n# libs.versions.toml\r\nnavigationCompose = \"2.8.3\"\r\n[libraries]\r\nandroidx-navigation-compose = { group = \"androidx.navigation\", name = \"navigation-compose\", version.ref = \"navigationCompose\" }\r\n```\r\n\r\n## Rule 1: Type-safe routes with @Serializable\r\n\r\n```kotlin\r\n// ✅ Define all routes as @Serializable objects/data classes\r\n// navigation/Routes.kt\r\n\r\n@Serializable\r\nobject HomeRoute\r\n\r\n@Serializable\r\nobject SearchRoute\r\n\r\n@Serializable\r\ndata class ItemDetailRoute(val itemId: String)\r\n\r\n@Serializable\r\ndata class EditItemRoute(val itemId: String, val isNew: Boolean = false)\r\n\r\n@Serializable\r\nobject SettingsRoute\r\n\r\n// ❌ String routes — typos compile, arguments are untyped\r\nnavController.navigate(\"detail/$itemId\")          // typo-prone\r\ncomposable(\"detail/{itemId}\") { backStackEntry ->\r\n    val id = backStackEntry.arguments?.getString(\"itemId\")  // nullable, untyped\r\n}\r\n```\r\n\r\n## Rule 2: NavHost setup\r\n\r\n```kotlin\r\n// ✅ Complete NavHost with type-safe destinations\r\n@Composable\r\nfun AppNavHost(\r\n    navController: NavHostController = rememberNavController(),\r\n    startDestination: Any = HomeRoute\r\n) {\r\n    NavHost(\r\n        navController = navController,\r\n        startDestination = startDestination\r\n    ) {\r\n        composable<HomeRoute> {\r\n            HomeScreen(\r\n                onItemClick = { itemId ->\r\n                    navController.navigate(ItemDetailRoute(itemId))\r\n                },\r\n                onSearchClick = { navController.navigate(SearchRoute) }\r\n            )\r\n        }\r\n\r\n        composable<SearchRoute> {\r\n            SearchScreen(\r\n                onItemClick = { itemId ->\r\n                    navController.navigate(ItemDetailRoute(itemId))\r\n                },\r\n                onBackClick = { navController.navigateUp() }\r\n            )\r\n        }\r\n\r\n        composable<ItemDetailRoute> { backStackEntry ->\r\n            val route: ItemDetailRoute = backStackEntry.toRoute()\r\n            ItemDetailScreen(\r\n                itemId = route.itemId,\r\n                onEditClick = { navController.navigate(EditItemRoute(route.itemId)) },\r\n                onBackClick = { navController.navigateUp() }\r\n            )\r\n        }\r\n\r\n        composable<EditItemRoute> { backStackEntry ->\r\n            val route: EditItemRoute = backStackEntry.toRoute()\r\n            EditItemScreen(\r\n                itemId = route.itemId,\r\n                isNew = route.isNew,\r\n                onSaved = { navController.navigateUp() },\r\n                onCancel = { navController.navigateUp() }\r\n            )\r\n        }\r\n\r\n        composable<SettingsRoute> {\r\n            SettingsScreen(onBackClick = { navController.navigateUp() })\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Rule 3: Navigate with launchSingleTop for tabs\r\n\r\n```kotlin\r\n// ✅ Tab navigation — prevent duplicate destinations on backstack\r\nfun NavController.navigateToTopLevel(route: Any) {\r\n    navigate(route) {\r\n        popUpTo(graph.findStartDestination().id) {\r\n            saveState = true\r\n        }\r\n        launchSingleTop = true\r\n        restoreState = true\r\n    }\r\n}\r\n\r\n// Usage in NavigationSuiteScaffold\r\nitem(\r\n    selected = currentDestination?.hasRoute<HomeRoute>() == true,\r\n    onClick = { navController.navigateToTopLevel(HomeRoute) }\r\n)\r\n```\r\n\r\n## Rule 4: Nested navigation graphs for feature isolation\r\n\r\n```kotlin\r\n// ✅ Nested graph for auth flow\r\n@Serializable\r\nobject AuthGraph\r\n\r\n@Serializable\r\nobject LoginRoute\r\n\r\n@Serializable\r\nobject RegisterRoute\r\n\r\n@Serializable\r\nobject ForgotPasswordRoute\r\n\r\n// In NavHost\r\nnavigation<AuthGraph>(startDestination = LoginRoute) {\r\n    composable<LoginRoute> {\r\n        LoginScreen(\r\n            onLoginSuccess = {\r\n                navController.navigate(HomeRoute) {\r\n                    popUpTo(AuthGraph) { inclusive = true }  // clear auth stack\r\n                }\r\n            },\r\n            onRegisterClick = { navController.navigate(RegisterRoute) },\r\n            onForgotPasswordClick = { navController.navigate(ForgotPasswordRoute) }\r\n        )\r\n    }\r\n    composable<RegisterRoute> { ... }\r\n    composable<ForgotPasswordRoute> { ... }\r\n}\r\n\r\n// Main app entry point decides which graph to show\r\nNavHost(startDestination = if (isLoggedIn) HomeRoute else AuthGraph) { ... }\r\n```\r\n\r\n## Rule 5: Shared ViewModel scoped to NavGraph\r\n\r\n```kotlin\r\n// ✅ Share ViewModel across multiple destinations in a nested graph\r\n@Serializable\r\nobject CheckoutGraph\r\n\r\n@Serializable\r\nobject CartRoute\r\n\r\n@Serializable\r\nobject ShippingRoute\r\n\r\n@Serializable\r\nobject PaymentRoute\r\n\r\n@Serializable\r\nobject OrderConfirmationRoute\r\n\r\n// NavHost\r\nnavigation<CheckoutGraph>(startDestination = CartRoute) {\r\n    composable<CartRoute> { entry ->\r\n        val parentEntry = remember(entry) {\r\n            navController.getBackStackEntry(CheckoutGraph)\r\n        }\r\n        val viewModel: CheckoutViewModel = hiltViewModel(parentEntry)\r\n        CartScreen(viewModel = viewModel)\r\n    }\r\n    composable<ShippingRoute> { entry ->\r\n        val parentEntry = remember(entry) {\r\n            navController.getBackStackEntry(CheckoutGraph)\r\n        }\r\n        val viewModel: CheckoutViewModel = hiltViewModel(parentEntry)\r\n        ShippingScreen(viewModel = viewModel)\r\n    }\r\n    // Same pattern for PaymentRoute, OrderConfirmationRoute\r\n}\r\n```\r\n\r\n## Rule 6: Deep links\r\n\r\n```kotlin\r\n// ✅ Deep link with type-safe route\r\ncomposable<ItemDetailRoute>(\r\n    deepLinks = listOf(\r\n        navDeepLink<ItemDetailRoute>(\r\n            basePath = \"https://myapp.com/items\"\r\n        )\r\n    )\r\n) { backStackEntry ->\r\n    val route: ItemDetailRoute = backStackEntry.toRoute()\r\n    ItemDetailScreen(itemId = route.itemId)\r\n}\r\n\r\n// AndroidManifest.xml — declare the intent filter\r\n<activity android:name=\".MainActivity\">\r\n    <intent-filter android:autoVerify=\"true\">\r\n        <action android:name=\"android.intent.action.VIEW\" />\r\n        <category android:name=\"android.intent.category.DEFAULT\" />\r\n        <category android:name=\"android.intent.category.BROWSABLE\" />\r\n        <data android:scheme=\"https\" android:host=\"myapp.com\" />\r\n    </intent-filter>\r\n</activity>\r\n```\r\n\r\n## Rule 7: Result passing between screens\r\n\r\n```kotlin\r\n// ✅ Use SavedStateHandle to pass results back\r\n// In the screen that receives the result\r\n@HiltViewModel\r\nclass HomeViewModel @Inject constructor(\r\n    savedStateHandle: SavedStateHandle\r\n) : ViewModel() {\r\n    val newItemId = savedStateHandle.getStateFlow<String?>(\"new_item_id\", null)\r\n}\r\n\r\n// In the screen that sends the result\r\n@Composable\r\nfun CreateItemScreen(\r\n    navController: NavController,\r\n    viewModel: CreateItemViewModel = hiltViewModel()\r\n) {\r\n    val onSave = {\r\n        navController.previousBackStackEntry\r\n            ?.savedStateHandle\r\n            ?.set(\"new_item_id\", viewModel.savedItemId)\r\n        navController.navigateUp()\r\n    }\r\n}\r\n```\r\n\r\n## Rule 8: Navigation transitions\r\n\r\n```kotlin\r\n// ✅ Custom enter/exit transitions per destination\r\ncomposable<ItemDetailRoute>(\r\n    enterTransition = {\r\n        slideInHorizontally(initialOffsetX = { it }) + fadeIn(tween(300))\r\n    },\r\n    exitTransition = {\r\n        slideOutHorizontally(targetOffsetX = { -it / 3 }) + fadeOut(tween(200))\r\n    },\r\n    popEnterTransition = {\r\n        slideInHorizontally(initialOffsetX = { -it / 3 }) + fadeIn(tween(200))\r\n    },\r\n    popExitTransition = {\r\n        slideOutHorizontally(targetOffsetX = { it }) + fadeOut(tween(300))\r\n    }\r\n) { ... }\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ String routes — use `@Serializable` data classes\r\n❌ `navController.navigate(route)` for tabs — always use `launchSingleTop = true`\r\n❌ Arguments as nullable strings from `arguments?.getString()` — use `backStackEntry.toRoute()`\r\n❌ ViewModel per Composable — scope to NavGraph when sharing state across screens\r\n❌ Missing `popUpTo` when navigating from auth to main flow — user can press back to login\r\n❌ Passing complex objects as navigation args — pass only IDs, load in destination ViewModel\r\n❌ `LocalContext.current` to get NavController — pass NavController or actions as lambdas","tags":["compose","navigation","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor"],"capabilities":["skill","source-piyushverma0","skill-compose-navigation","topic-agent-skills","topic-ai-agent","topic-android","topic-antigravity","topic-claude-code","topic-codex","topic-cursor","topic-gemini-cli","topic-hilt","topic-jetpack-compose","topic-kotlin","topic-material3"],"categories":["android-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/piyushverma0/android-agent-skills/compose-navigation","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add piyushverma0/android-agent-skills","source_repo":"https://github.com/piyushverma0/android-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (21,886 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:09.258Z","embedding":null,"createdAt":"2026-05-18T13:14:48.828Z","updatedAt":"2026-05-18T19:09:09.258Z","lastSeenAt":"2026-05-18T19:09:09.258Z","tsv":"'/items':748,1379 '/items/':752 '0':944 '1':188,1033 '1.4':572 '16f':661,710 '18':35 '2':301,1101 '2.8':569,895 '2.8.3':1018 '2.8.5':174 '200':1479,1487 '27':4 '3':483,1180,1476,1484 '300':1471,1494 '4':353,366,552,1221 '5':30,727,1288 '6':766,1361 '7':830,1394 '8':886,1455 '9f':662,711 'across':775,1297,1529 'action':1566 'add':1007 'agent':9,75 'ai':8,32,51,74 'ai-built':50 'alway':133,1507 'android':5,73 'androidmanifest.xml':762,1388 'androidx':1021 'androidx-navigation-compos':1020 'androidx.navigation':180,1025 'anim':113,909,917,999 'animatedcont':581,603,616 'animatedvisibilityscop':601,614,636,637,657,658,671,672,687,688,706,707,722,723 'animationspec':341,354,367,379 'anywher':930 'app':53,169,1272 'appeasing.emphasizedaccel':358,383 'appeasing.emphasizeddecel':345,371 'appli':134,331 'appnavhost':311,1114 'arg':981,1551 'argument':96,122,222,1080,1511,1516 'aspectratio':660,709 'asyncimag':645,694 'auth':16,441,458,1232,1261,1536 'authgraph':256,321,454,473,965,1236,1257,1286 'automat':899 'back':91,110,155,838,888,898,906,914,973,1405,1543 'backhandl':915 'backstack':499,1193 'backstackentri':294,1092,1146,1161,1380 'backstackentry.arguments':295,1095 'backstackentry.toroute':1150,1165,1384,1519 'basepath':745,1376 'bill':29 'boolean':239,250,1071 'bottom':116 'bug':48 'built':52 'card':642 'cartrout':783,1309,1322 'cartscreen':797,1336 'checkoutgraph':791,806,821,1306,1330,1346 'checkoutviewmodel':794,809,824,1333,1349 'class':225,232,243,870,934,1047,1057,1064,1414,1502 'claud':10 'clear':457,1260 'code':11 'codex':12 'column':693 'common':46,926,1495 'compil':1079 'complet':58,76,143,302,1105 'complex':165,977,1547 'compos':2,37,66,70,97,132,141,178,184,291,309,391,395,399,403,407,413,428,448,467,478,575,629,680,741,784,799,814,847,1023,1029,1089,1112,1126,1136,1145,1160,1175,1251,1269,1270,1323,1339,1372,1436,1464,1522 'compose-navig':1,65 'constructor':873,1417 'contain':253 'contentdescript':648,697 'coroutinescope.launch':916 'cover':56,158 'creat':524 'createitemscreen':849,1438 'createitemviewmodel':1442 'currentdestin':545,1214 'cursor':13 'custom':905,1459 'data':224,231,242,1056,1063,1501 'day':36 'decid':1275 'declar':1389 'deep':93,728,736,1362,1365 'deeplink':742,1373 'default':336 'defin':200,1041 'delay':923 'descript':68 'design':19 'destin':305,390,510,740,841,843,865,1111,1191,1299,1463,1557 'detail':283,292,411,566,674,995,1084,1090 'duplic':490,525,1190 'duration.medium4':343,348,369,374 'duration.short4':356,361,381,386 'duration.short4.tolong':924 'eas':344,357,370,382 'edititemrout':233,424,432,1065,1156,1164 'edititemscreen':434,1166 'element':107,151,554,560,628,678 'els':203,320,610,1285 'enabl':889 'enter/exit':329,1460 'enter/exit/pop':1008 'entertransit':337,1465 'entir':460 'entri':414,429,491,754,785,789,800,804,815,819,962,1273,1324,1328,1340,1344 'entry.toroute':418,433,758 'error':18 'everi':159,304,954 'exittransit':349,1472 'express':328,557 'fadein':346,372,1469,1485 'fadeout':359,384,1477,1492 'fals':240,251,922,1072 'featur':1226 'fetch':985 'fillmaxwidth':659,708 'filter':764,1392 'fitgenz':31 'fix':14 'flow':442,1233,1539 'forgotpasswordrout':265,466,1245,1268 'forgotpasswordscreen':479 'fromnotif':249 'fun':310,501,576,630,681,848,1113,1194,1437 'gestur':111 'get':1561 'getstr':296,1096,1517 'graph':99,168,252,444,780,947,1224,1230,1277,1303 'graph.findstartdestination':516,950,1201 'group':179,1024 'guid':144 'handl':896 'handler':907 'hasrout':546,1215 'helper':494 'hilt':17,120 'hiltviewmodel':795,810,825,869,952,1334,1350,1413,1443 'homerout':206,319,452,471,550,1051,1120,1219,1255,1284 'homescreen':392,577,1127 'homeviewmodel':871,1415 'id':517,861,883,951,984,1094,1202,1427,1451,1554 'imag':655,704 'inboxrout':212 'inboxscreen':400 'inclus':455,474,966,1258 'inconsist':20 'initialoffsetx':339,364,1467,1482 'inject':872,1416 'intent':763,1391 'isloggedin':318,1283 'isnew':238,437,1070,1169 'isol':1227 'isvis':921 'it.id':596 'item':497,543,564,588,590,593,594,597,599,600,609,612,613,625,632,633,683,684,860,882,1212,1426,1450 'item.id':656,670,705,721 'item.imageurl':647,696 'item.title':664,698,713 'itemcard':598,631 'itemdetailrout':226,417,757,1058,1131,1141,1149,1383 'itemdetailscreen':419,611,682,759,1151,1385 'itemid':228,235,284,293,297,420,435,753,760,1060,1067,1085,1091,1097,1129,1132,1139,1142,1152,1167,1386 'jetpack':69 'kapt':21 'key':595,679,989 'kotlin':196,307,492,558,735,772,835,893,1040,1104,1186,1228,1294,1364,1399,1458 'ksp':22 'label':584 'lambda':1568 'launchsingletop':104,488,520,1183,1205,1509 'lazycolumn':592 'level':389,486,1003 'leveldeep':1012 'librari':175,1019 'libs.versions.toml':1016 'link':94,729,737,1013,1363,1366 'list':563,624,993 'listof':743,1374 'load':1555 'localcontext.current':1559 'login':969,975,1545 'loginrout':259,447,1239,1250 'loginscreen':449,1252 'm3':327,556 'main':1271,1538 'manifest':891 'mark':626 'match':749,991 'material3':571 'materialtheme.typography.headlinemedium':715 'miss':23,963,1531 'mistak':927,1496 'model':646,695 'modifi':650,651,665,699,700,716 'modifier.sharedelement':666,717 'multi':167 'multi-graph':166 'multipl':776,1298 'myapp.com':747,751,1378 'myapp.com/items':746,1377 'myapp.com/items/':750 'name':64,181,1026 'navcontrol':85,312,323,324,393,394,397,398,401,402,405,406,578,579,850,851,1115,1122,1123,1439,1440,1562,1564 'navcontroller.getbackstackentry':790,805,820,1329,1345 'navcontroller.navigate':282,423,451,462,465,470,935,941,1083,1130,1134,1140,1155,1254,1264,1267,1503 'navcontroller.navigatetoplevel':502,549 'navcontroller.navigatetotoplevel':1195,1218 'navcontroller.navigateup':410,427,440,477,481,863,925,1144,1159,1172,1174,1178,1453 'navcontroller.previousbackstackentry':856,1446 'navdeeplink':744,1375 'navgraph':771,961,1293,1525 'navhost':84,138,303,322,894,913,1006,1011,1102,1106,1121,1247,1280,1319 'navhostcontrol':313,1116 'navig':3,38,42,47,59,67,71,83,95,117,118,129,142,148,177,183,445,487,493,505,537,568,781,1022,1028,1181,1188,1198,1223,1248,1320,1456,1534,1550 'navigatetoplevel':103,940 'navigateup':102 'navigation-compos':176,182,1027 'navigation/appnavhost.kt':308 'navigation/routes.kt':197,1048 'navigationcompos':173,186,1017,1031 'navigationsuitescaffold':542,1211 'nest':98,443,1222,1229,1302 'never':278 'new':859,881,1425,1449 'newitemid':854,862,878,1422 'non':912 'non-navhost':911 'nowher':202 'null':591,622,649,884,1428 'nullabl':298,1098,1513 'object':205,208,211,214,217,255,258,261,264,267,270,273,276,978,1050,1053,1074,1235,1238,1241,1244,1305,1308,1311,1314,1317,1548 'object/data':933 'objects/data':1046 'onback':409,426,476,480,620,689 'onbackclick':1143,1158,1177 'onboardinggraph':268 'onboardingstep1route':271 'onboardingstep2route':274 'onboardingstep3route':277 'oncancel':1173 'onclick':548,607,638,643,644,1217 'ondon':439 'onedit':422 'oneditclick':1154 'onforgotpass':464 'onforgotpasswordclick':1266 'onitemclick':1128,1138 'onloginsuccess':450,1253 'onregisterclick':1263 'onsav':853,1171,1445 'onsearchclick':1133 'onsignup':461 'onsuccess':469 'orderconfirmationrout':1318,1359 'pad':724 'parententri':787,796,802,811,817,826,1326,1335,1342,1351 'pass':121,124,832,836,976,982,1396,1403,1546,1552,1563 'past':946 'pattern':60,77,130,160,1356 'paymentrout':1315,1358 'paymentscreen':827 'per':1462,1521 'point':1274 'pollut':500 'pop':507,920,945 'popentertransit':362,1480 'popexittransit':375,1488 'popupto':105,453,472,515,943,964,1200,1256,1532 'predict':109,154,887,897 'press':972,1542 'prevent':43,489,498,1189 'previous':840 'produc':845 'profilerout':215 'profilescreen':404 'prone':287,1088 're':528,536 're-navig':535 're-tap':527 'receiv':867,1410 'reduc':26 'regist':306,738 'registerrout':1242,1265 'rememb':788,803,818,1327,1343 'remembernavcontrol':314,1117 'remembersharedcontentst':654,668,703,719 'requir':221 'restor':514,532 'restorest':530,1207 'result':123,831,837,846,868,1395,1404,1412,1435 'root':948 'rout':90,140,163,190,195,199,219,281,416,431,503,506,734,756,904,929,936,980,1037,1043,1077,1148,1163,1196,1199,1371,1382,1498,1504 'route.isnew':438,1170 'route.itemid':421,425,436,761,1153,1157,1168,1387 'rule':55,157,187,300,482,551,726,765,829,885,1032,1100,1179,1220,1287,1360,1393,1454 'safe':41,88,147,733,903,1036,1110,1370 'safeti':290 'save':511 'savedstatehandl':126,834,857,874,875,1401,1418,1419,1447 'savedstatehandle.getstateflow':879,1423 'savest':518,1203 'scope':769,959,1291,1523 'screen':115,334,412,567,675,777,955,1002,1398,1408,1431,1530 'screen-level':1001 'searchrout':209,1054,1135 'searchscreen':396,1137 'select':544,1213 'selecteditem':583,608,621 'send':1433 'serializ':89,192,204,207,210,213,216,223,230,241,254,257,260,263,266,269,272,275,932,1039,1045,1049,1052,1055,1062,1073,1234,1237,1240,1243,1304,1307,1310,1313,1316,1500 'set':858,1448 'settingsrout':218,1075 'settingsscreen':408,1176 'setup':62,170,1014,1103 'share':100,106,150,553,559,586,627,677,767,773,957,1289,1295,1527 'shared-transit':585 'sharedel':652,701,988 'sharedtransitionlayout':574,580,606,619 'sharedtransitionscop':604,617,634,635,641,685,686,692 'ship':33 'shippingrout':1312 'shippingscreen':812,1352 'show':1279 'signuprout':262,463 'signupscreen':468 'simpl':162 'skill':6,80 'skill-compose-navigation' 'slideinhorizont':338,363,1466,1481 'slideouthorizont':350,376,1473,1489 'somerout':942 'source-piyushverma0' 'spacing.md':725 'spec':1009 'stack':92,459,1262 'start':509 'startdestin':315,325,326,446,782,1118,1124,1125,1249,1281,1321 'state':25,512,533,653,667,702,718,774,958,1528 'string':194,229,236,247,280,855,880,928,1061,1068,1076,1424,1497,1514 'style':714 'supabas':15 'support':573 'tab':539,938,1185,1187,1506 'tab/nav':496 'tap':529 'targetoffsetx':351,377,1474,1490 'targetst':582 'text':663,712 'titl':669,720 'token':28 'toml':171,1015 'top':388,485 'top-level':387,484 'topic-agent-skills' 'topic-ai-agent' 'topic-android' 'topic-antigravity' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-gemini-cli' 'topic-hilt' 'topic-jetpack-compose' 'topic-kotlin' 'topic-material3' 'transit':108,112,152,330,555,561,587,996,1004,1457,1461 'true':456,475,519,521,531,547,967,1204,1206,1208,1216,1259,1510 'tween':342,347,355,360,368,373,380,385,1470,1478,1486,1493 'type':40,87,146,289,732,902,1035,1109,1369 'type-saf':39,86,145,731,901,1034,1108,1368 'typo':286,1078,1087 'typo-pron':285,1086 'uistat':24 'unit':639,690 'untyp':299,1082,1099 'usag':540,1209 'use':78,279,931,939,949,1400,1499,1508,1518 'user':970,1540 'userid':246 'userprofilerout':244 'val':227,234,237,245,248,415,430,755,786,792,801,807,816,822,852,877,1059,1066,1069,1093,1147,1162,1325,1331,1341,1347,1381,1421,1444 'version':172 'version.ref':185,1030 'via':125,833 'viewmodel':101,768,793,798,808,813,823,828,876,987,1290,1296,1332,1337,1338,1348,1353,1354,1420,1441,1520,1558 'viewmodel.saveditemid':1452 'won':997 'write':136 'xml':892 'zero':193","prices":[{"id":"b5222d9b-cd9f-481f-aed0-f07ac3643056","listingId":"34c55978-d0ab-45ac-ad34-a99ce8ec4e5a","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"piyushverma0","category":"android-agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:48.828Z"}],"sources":[{"listingId":"34c55978-d0ab-45ac-ad34-a99ce8ec4e5a","source":"github","sourceId":"piyushverma0/android-agent-skills/compose-navigation","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/compose-navigation","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:48.828Z","lastSeenAt":"2026-05-18T19:09:09.258Z"}],"details":{"listingId":"34c55978-d0ab-45ac-ad34-a99ce8ec4e5a","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"compose-navigation","github":{"repo":"piyushverma0/android-agent-skills","stars":8,"topics":["agent-skills","ai-agent","android","antigravity","claude-code","codex","cursor","gemini-cli","hilt","jetpack-compose","kotlin","material3","open-source","skills","supabase"],"license":"mit","html_url":"https://github.com/piyushverma0/android-agent-skills","pushed_at":"2026-04-27T09:15:31Z","description":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","skill_md_sha":"1aa25ccf3dd11b61203534cac02b2fb34d32e972","skill_md_path":"skills/compose-navigation/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/compose-navigation"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/compose-navigation"},"updatedAt":"2026-05-18T19:09:09.258Z"}}