원클릭으로
navigation-adaptive-ui
Best practices for Type-Safe Navigation (Nav 3) and Adaptive Layouts in Upnext.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Best practices for Type-Safe Navigation (Nav 3) and Adaptive Layouts in Upnext.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Best practices for Android instrumentation tests, Hilt testing, Mockito configuration, and test resilience patterns in Upnext.
Strict guidelines and workflow for developing new features, ensuring quality, proper branching, UI/UX standards, and comprehensive testing in Upnext.
Expert capabilities for managing Upnext application releases, CI/CD pipelines, and versioning.
Guidelines and strict rules for Kotlin code formatting, specifically concerning imports, fully qualified names, and avoiding codebase pollution.
Core product design and development principles for Upnext, derived from competitive analysis of the official Trakt app's negative user feedback. Use this skill when proposing new features, designing UI/UX, or prioritizing the roadmap.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications.
| name | Navigation & Adaptive UI |
| description | Best practices for Type-Safe Navigation (Nav 3) and Adaptive Layouts in Upnext. |
This skill encapsulates the critical patterns for implementing adaptive layouts and navigation in the Upnext application. It specifically addresses "Gotchas" related to ListDetailPaneScaffold and Compose Navigation 3.
Upnext uses NavigableListDetailPaneScaffold to support phones, tablets, and foldables.
[!NOTE] Adaptive UI utilities, such as
WindowSizeClassUtiland the@ReferenceDevicesPreview annotation, are now centralized in the:core:designsystemmodule to prevent circular dependencies. Ensure feature modules depend on:core:designsystemto use them.
NEVER use rememberSupportingPaneScaffoldNavigator with NavigableListDetailPaneScaffold. This causes back navigation conflicts and UI flickering.
✅ Correct Usage:
// MainScreen.kt
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator<Any>()
NavigableListDetailPaneScaffold(
navigator = listDetailNavigator,
defaultBackBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange, // Let scaffold handle pane back
// ...
)
The scaffold handles back navigation between panes (Detail -> List) automatically if defaultBackBehavior is set correctly.
BackHandler if the scaffold can handle it.BackHandler only for top-level destination changes (e.g., Explore -> Dashboard).When returning to the list view from an external intent (like an OAuth browser callback), the navigation graphs may become out of sync, leading to IllegalArgumentException: Destination with route cannot be found.
EmptyDetail destination to gracefully clear the view. The scaffold will interpret the empty detail state and automatically jump back to the list cleanly logic!// Example callback reset
mainNavController.navigate(Destinations.EmptyDetail) {
popUpTo(Destinations.EmptyDetail) { inclusive = true }
}
We use the official Compose Navigation 3 with Kotlin Serialization.
Routes are defined in Destinations.kt as @Serializable classes or objects.
@Serializable
object Dashboard : Destinations
@Serializable
data class ShowDetail(
val showId: String?,
val showTitle: String?
) : Destinations
Under Navigation 3, the back stack is developer-owned, typically managed via a SnapshotStateList<Any>:
// Navigate by adding keys directly to the back stack state list
backStack.add(Destinations.ShowDetail(showId = "123", showTitle = "Arcane"))
Because the back stack keys themselves are the typed route instances, extracting arguments (e.g., for a TopBar title) is simple pattern matching:
✅ Correct Pattern:
// AppNavigation.kt
val currentKey = backStack.lastOrNull()
val showTitle = when (currentKey) {
is Destinations.ShowDetail -> currentKey.showTitle
is Destinations.PersonDetail -> currentKey.personName
else -> null
}
In Navigation 3, the runtime does not automatically bundle and inject the route arguments into the default SavedStateHandle. Calling toRoute<T>() in the ViewModel constructor will crash with a deserialization failure.
✅ Correct Pattern:
Use @AssistedInject and @AssistedFactory in your ViewModels, and resolve them using the creationCallback on hiltViewModel:
// ViewModel
class EpisodeDetailViewModel @AssistedInject constructor(
@Assisted val route: Destinations.EpisodeDetail,
// ...
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(route: Destinations.EpisodeDetail): EpisodeDetailViewModel
}
}
// Screen Composable
val viewModel: EpisodeDetailViewModel = hiltViewModel(
creationCallback = { factory: EpisodeDetailViewModel.Factory ->
factory.create(episodeDetailArg)
}
)
ShowDetail) when the destination matches.isDetailFlowActive) inside a LaunchedEffect that observes that same state without proper checks.!!) on LazyRow or LazyVerticalGrid items(list!!) when the underlying data stream is briefly interrupted (e.g. during a Trakt logout where the Database momentarily clears its snapshot before the Viewmodel drops the authorization state)..orEmpty() on Compose list arguments, like items(list.orEmpty()), ensuring Compose safely renders zero items instead of instantly crashing the active Activity during auth transitions.NavigationSuiteScaffold (Bottom Bar / Rail) should wrap the content.