kmp-navigation
Skill4 starsChanged 4 months agoMIT-0View on GitHub
Compose Navigation for TV: state-based screen switching, BackHandler, focus-aware transitions, and drawer/sidebar patterns
2 other projects use a version of this.
Adopted by
Most recently taken up by
- PL-Coding-GmbH/Skillstheir own version, first seen Nov 8, 2026
- ronjunevaldoz/kmp-agent-skillstheir own version, first seen Aug 2, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- PL-Coding-GmbH/Skills★ 18same name
- ronjunevaldoz/kmp-agent-skills★ 5same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- 10ft-uiSkill
- amazon-devices-vega-app-performanceSkill
- amazon-devices-vega-best-practicesSkill
- amazon-devices-vega-setup-sdkSkill
- android-tv-testingSkill
- changelog-static-siteSkill
- creative-tv-uiSkill
- eas-buildSkill
- expo-tv-configSkill
- firetv-leanbackSkill
- kmp-build-commandsSkill
- kmp-compose-tvSkill
- kmp-data-layerSkill
- kmp-template-anatomySkill
- kmp-themingSkill
- kmp-verify-patternsSkill
- metaSkill
- rn-manifest-wiringSkill
- rn-shared-ui-catalogSkill
- rn-spatial-navigationSkill
- rn-template-anatomySkill
- rn-themingSkill
- vega-sdkSkill
- video-playerSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 64
Always provide enabled parameter scoped to the current screen.
- Distinctive: line 179
Opening: When user presses D-pad Left at the leftmost focusable item, drawer expands and receives focus.
- Distinctive: line 203
Deep nesting of when blocks: Keep navigation flat. If you have more than 4-5 screens, consider an enum-based approach:
- Distinctive: line 208
Animated transitions that steal focus: If focus disappears after a screen switch, the transition composable is likely consuming the focus …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
KMP Navigation
The kmptv template uses state-based navigation (not Jetpack Navigation Compose). This is intentional — Navigation Compose has known focus-restoration issues on TV that make it unreliable for D-pad apps.
Navigation model
The Android TV app manages navigation via state variables in MainScreen():
@Composable
private fun MainScreen() {
var selectedItem by remember { mutableStateOf<ContentItem?>(null) }
var showingDetail by rememberSaveable { mutableStateOf(false) }
var showingVideoPlayer by rememberSaveable { mutableStateOf(false) }
BackHandler(enabled = showingVideoPlayer || showingDetail) {
if (showingVideoPlayer) {
showingVideoPlayer = false
} else if (showingDetail) {
showingDetail = false
selectedItem = null
}
}
when {
showingVideoPlayer && selectedItem?.videoUrl != null ->
VideoPlayerScreen(item = selectedItem!!, onBack = { showingVideoPlayer = false })
showingDetail && selectedItem != null ->
ContentDetailScreen(item = selectedItem!!, onBack = { showingDetail = false })
else ->
HomeScreen(onItemClick = { item ->
selectedItem = item
showingDetail = true
})
}
}Why state-based, not NavHost
- Focus restoration: NavHost loses focus position when popping the back stack. TV users expect to return to the exact card they left.
- Simplicity: Three screens with boolean flags is easier to reason about than a NavGraph with arguments.
- Animation control:
Crossfadeand custom transitions work naturally withwhenblocks. - rememberSaveable: Screen state survives configuration changes without NavGraph SavedState complexity.
BackHandler — back stack behavior
import androidx.activity.compose.BackHandler
BackHandler(enabled = /* when this screen is active */) {
// Pop this screen
}Rules:
- Always provide
enabledparameter scoped to the current screen. Without it, back handling fires when it shouldn't. - Priority: innermost enabled BackHandler wins. So the player's BackHandler fires before the detail's.
- On the home screen, don't add a BackHandler — let the system handle app exit.
Back stack ordering
Home (no BackHandler — system exits app)
└── Detail (BackHandler → goes back to Home)
└── VideoPlayer (BackHandler → goes back to Detail)Adding a new screen
- Create the composable in
androidtv-app/src/main/java/.../compose/:
@Composable
fun NewScreen(
item: ContentItem,
onBack: () -> Unit,
) {
// Screen content
}- Add state variable in MainScreen:
var showingNewScreen by rememberSaveable { mutableStateOf(false) }- Add to the
whenblock (order matters — more specific screens first):
when {
showingVideoPlayer -> VideoPlayerScreen(...)
showingNewScreen -> NewScreen(item = selectedItem!!, onBack = { showingNewScreen = false })
showingDetail -> ContentDetailScreen(...)
else -> HomeScreen(...)
}- Add BackHandler entry:
BackHandler(enabled = showingNewScreen) {
showingNewScreen = false
}Screen transitions
The template uses Crossfade for the hero banner. For screen transitions, the when block provides an instant swap. To add animated transitions:
AnimatedContent(
targetState = currentScreen,
transitionSpec = {
fadeIn(tween(300)) togetherWith fadeOut(tween(300))
}
) { screen ->
when (screen) {
Screen.Home -> HomeScreen(...)
Screen.Detail -> ContentDetailScreen(...)
Screen.Player -> VideoPlayerScreen(...)
}
}Caution: Complex transitions can interfere with focus. Test that focus lands correctly after every transition.
Drawer / sidebar navigation (pattern for multi-section apps)
For apps with multiple top-level sections (e.g., Home, Movies, TV Shows, Settings), add a navigation drawer:
@Composable
fun AppWithDrawer() {
var currentSection by rememberSaveable { mutableStateOf(Section.Home) }
var drawerOpen by remember { mutableStateOf(false) }
val drawerFocusRequester = remember { FocusRequester() }
Row(Modifier.fillMaxSize()) {
// Drawer panel (collapsed by default, expands on focus/dpad-left)
NavigationDrawer(
isOpen = drawerOpen,
sections = Section.values().toList(),
selectedSection = currentSection,
onSectionSelected = { section ->
currentSection = section
drawerOpen = false
},
modifier = Modifier.focusRequester(drawerFocusRequester),
)
// Content area
Box(Modifier.weight(1f)) {
when (currentSection) {
Section.Home -> HomeScreen(...)
Section.Movies -> CategoryScreen(genre = "Movies")
Section.Settings -> SettingsScreen()
}
}
}
// Open drawer on left D-pad at screen edge
BackHandler(enabled = drawerOpen) {
drawerOpen = false
}
}Drawer focus behavior
- Opening: When user presses D-pad Left at the leftmost focusable item, drawer expands and receives focus.
- Closing: Selecting an item or pressing D-pad Right returns focus to content.
- Focus trap: While open, focus stays within the drawer. D-pad Up/Down navigates items.
Apple TV navigation (SwiftUI)
The Apple TV app uses SwiftUI's NavigationStack:
NavigationStack {
HomeView()
.navigationDestination(for: ContentItem.self) { item in
ContentDetailView(item: item)
}
}SwiftUI handles focus restoration automatically on tvOS. The back button (Menu on Siri Remote) pops the stack natively.
Anti-patterns
- Using Jetpack Navigation Compose (NavHost) for TV. Focus restoration is broken — after
popBackStack(), focus jumps to the first focusable instead of the previously focused item. Use state-based navigation. - Forgetting
rememberSaveablefor screen state.rememberloses state on configuration change. UserememberSaveablefor navigation booleans. - Global BackHandler without
enabledguard. Intercepts back presses on every screen. Always scope withenabled = specificScreenIsActive. - Deep nesting of when blocks. Keep navigation flat. If you have more than 4-5 screens, consider an enum-based approach:
enum class Screen { Home, Detail, Player, Settings } var currentScreen by rememberSaveable { mutableStateOf(Screen.Home) } - Animated transitions that steal focus. If focus disappears after a screen switch, the transition composable is likely consuming the focus event. Test with D-pad after every transition.
- Not clearing selectedItem on back. Stale state causes crashes when the next screen reads a null item. Always reset associated state when popping.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

