skills/jetpack-compose-navigation-expert/SKILL.md
Jetpack Compose navigation expert with type-safe routes, Hilt DI, and MVVM/MVI architecture. Activate on: Jetpack Compose navigation, Compose type-safe routes, Hilt dependency injection, MVVM Android, MVI pattern, Compose state management, NavHost, ViewModel. NOT for: XML layouts (use frontend-architect), iOS SwiftUI (use swiftui-data-flow-expert), React Native (use react-native-architect).
npx skillsauth add curiositech/windags-skills jetpack-compose-navigation-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in Jetpack Compose navigation with type-safe routes, Hilt dependency injection, and clean MVVM/MVI architecture.
Activate on: "Jetpack Compose navigation", "Compose type-safe routes", "Hilt DI", "MVVM Android", "MVI pattern", "Compose state management", "NavHost", "ViewModel Compose", "navigation-compose"
NOT for: XML layouts → frontend-architect | iOS SwiftUI → swiftui-data-flow-expert | React Native → react-native-architect
implementation("androidx.navigation:navigation-compose:2.9.x") with type-safe routes@Serializable data class ProductRoute(val id: String)@HiltViewModel for ViewModels with constructor injectioncollectAsStateWithLifecycle| Domain | Technologies | |--------|-------------| | Navigation | Navigation Compose 2.9, type-safe routes, nested graphs | | DI | Hilt 2.53, @HiltViewModel, @Inject, module scoping | | Architecture | MVVM, MVI (Circuit, Mavericks), UDF (unidirectional data flow) | | State | StateFlow, collectAsStateWithLifecycle, SavedStateHandle | | Async | Kotlin Coroutines, Flow, Room with Flow, Retrofit suspend |
// Define routes as serializable data classes
@Serializable
data object HomeRoute
@Serializable
data class ProductRoute(val productId: String)
@Serializable
data class OrderRoute(val orderId: String, val showConfirmation: Boolean = false)
// NavHost with type-safe composable destinations
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController = navController, startDestination = HomeRoute) {
composable<HomeRoute> {
HomeScreen(
onProductClick = { id ->
navController.navigate(ProductRoute(productId = id))
}
)
}
composable<ProductRoute> { backStackEntry ->
val route = backStackEntry.toRoute<ProductRoute>()
ProductScreen(productId = route.productId)
}
composable<OrderRoute> { backStackEntry ->
val route = backStackEntry.toRoute<OrderRoute>()
OrderScreen(orderId = route.orderId)
}
}
}
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repository: ProductRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val productId: String = savedStateHandle.toRoute<ProductRoute>().productId
private val _uiState = MutableStateFlow<ProductUiState>(ProductUiState.Loading)
val uiState: StateFlow<ProductUiState> = _uiState.asStateFlow()
init { loadProduct() }
fun onEvent(event: ProductEvent) {
when (event) {
is ProductEvent.AddToCart -> addToCart(event.quantity)
is ProductEvent.Refresh -> loadProduct()
}
}
private fun loadProduct() {
viewModelScope.launch {
_uiState.value = ProductUiState.Loading
repository.getProduct(productId)
.onSuccess { _uiState.value = ProductUiState.Success(it) }
.onFailure { _uiState.value = ProductUiState.Error(it.message) }
}
}
}
// Composable observes state
@Composable
fun ProductScreen(viewModel: ProductViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (val state = uiState) {
is ProductUiState.Loading -> LoadingIndicator()
is ProductUiState.Success -> ProductContent(
product = state.product,
onEvent = viewModel::onEvent,
)
is ProductUiState.Error -> ErrorMessage(state.message)
}
}
Root NavHost
├─ Auth Graph (unauthenticated)
│ ├─ Login
│ ├─ Register
│ └─ ForgotPassword
├─ Main Graph (authenticated)
│ ├─ Home Tab
│ │ ├─ Feed
│ │ └─ Product Detail
│ ├─ Search Tab
│ │ ├─ Search
│ │ └─ Category
│ └─ Profile Tab
│ ├─ Profile
│ └─ Settings
└─ Fullscreen overlays (shared)
├─ ImageViewer
└─ VideoPlayer
navController.navigate("product/123") is error-prone. Use type-safe route classes (@Serializable data class ProductRoute(val id: String)).hiltViewModel() which handles scoping to the NavBackStackEntry.collectAsState() keeps collecting when the app is backgrounded. Use collectAsStateWithLifecycle() to respect lifecycle.navController.navigate() from ViewModel. Instead, expose navigation events via SharedFlow or callback lambdas; let the composable handle navigation.[ ] Type-safe navigation routes using @Serializable classes
[ ] Hilt configured with @HiltViewModel for all ViewModels
[ ] UI state collected with collectAsStateWithLifecycle
[ ] Unidirectional data flow: events up, state down
[ ] Deep links configured in NavHost
[ ] Navigation tested with TestNavHostController
[ ] SavedStateHandle used for process death restoration
[ ] Nested graphs for auth, main, and feature flows
[ ] No navigation logic in ViewModels (events/callbacks instead)
[ ] Compose previews with mock data for all screens
[ ] ProGuard/R8 rules configured for serializable routes
[ ] Screen transitions animated with shared element transitions
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.