Status Update
Comments
jb...@google.com <jb...@google.com>
il...@google.com <il...@google.com> #2
The whole idea above is to have
Screen*Destination
classes centralized somewhere and not referenced across the whole project, but the library does not allow to declare screens like that.
Correct, this is not a scalable solution as explained in the
Can you explain why exactly you are trying to centralize all of your classes in one place?
es...@gmail.com <es...@gmail.com> #3
Can you also expose non-reified navigate while you're at it? I'm trying to achieve the same use case but with navGraphs. I can't use the overload since it is only limited to the library. This helps identify the heirarchy in a type-safe navigable way using a parent interface for graphs and destinations. Each could be encapsulated then brought into 1 class in some place.
pa...@gmail.com <pa...@gmail.com> #4
Can you explain why exactly you are trying to centralize all of your classes in one place?
The idea is to have one place where the Screen*Destination
classes live so that when we need to modify/add/remove something related to them the devs know where to look.
This problem is exacerbated by the BottomNavigation
composable when we need to decide whether the bottom bar should be visible or whether the BottomNavigationItem
is currently selected. The solution to these two problems requires us to reference Screen*Destination
classes, just like the Screen
class from above, I don't really care about any Screen*Destination
classes and can operate by my own:
@Composable
internal fun BottomBar(
navController: NavHostController,
currentScreen: Screen,
) {
val isBottomBarScreenDisplayed = remember(currentScreen) {
BottomNavigationItemModel
.entries
.any { itemModel -> itemModel.screen == currentScreen }
}
LaunchedEffect(isBottomBarScreenDisplayed) {
navController.enableOnBackPressed(!isBottomBarScreenDisplayed)
}
AnimatedVisibility(
visible = isBottomBarScreenDisplayed,
enter = slideInVertically(
animationSpec = tween(BOTTOM_BAR_ANIMATION_DURATION),
initialOffsetY = { it },
),
exit = slideOutVertically(
animationSpec = tween(BOTTOM_BAR_ANIMATION_DURATION),
targetOffsetY = { it },
),
) {
BottomBarNavigation(
navController = navController,
currentScreen = currentScreen,
)
}
}
@Composable
private fun BottomBarNavigation(
navController: NavHostController,
currentScreen: Screen,
) {
BottomNavigation(windowInsets = WindowInsets.navigationBars) {
for (bottomNavigationItemModel in BottomNavigationItemModel.entries) {
val itemScreen = bottomNavigationItemModel.screen
BottomNavigationItem(
selected = currentScreen == itemScreen,
onClick = { ... },
icon = { ... },
label = { ... },
)
}
}
}
private enum class BottomNavigationItemModel(
@DrawableRes
val iconId: Int,
@StringRes
val titleId: Int,
val screen: Screen,
) {
SCREEN_A(
iconId = ...,
titleId = ...,
screen = Screen.ScreenA,
),
SCREEN_B(
iconId = ...,
titleId = ...,
screen = Screen.ScreenB,
),
SCREEN_C(
iconId = ...,
titleId = ...,
screen = Screen.ScreenC,
),
}
The bottom nav bar is just one example where I need to reference the Screen*Destination
classes. I've found another case related to the destination's animations where I need to know the exact screen I'm navigating to or from to know which animation to play. By having the Screen
class from above, I can have this nice and simple implementation:
private fun NavGraphBuilder.screenA(navController: NavHostController) {
composable(
route = Screen.ScreenA.routeClass,
enterTransition = {
when (Screen.forRoute(initialState.destination.requireRoute())) {
Screen.ScreenB -> OvershootScaling.enter()
else -> null
}
},
exitTransition = {
when (Screen.forRoute(targetState.destination.requireRoute())) {
Screen.ScreenC -> HorizontalSliding.exit()
else -> null
}
},
popEnterTransition = {
when (Screen.forRoute(initialState.destination.requireRoute())) {
Screen.ScreenC -> HorizontalSliding.popEnter()
else -> null
}
},
popExitTransition = {
when (Screen.forRoute(targetState.destination.requireRoute())) {
Screen.ScreenA -> OvershootScaling.popExit()
else -> null
}
},
) {
Description
Version used: `2.8.0-rc01`
The library exposes only the `reified` variant of the `composable` extension:
```
public inline fun <reified T : Any> NavGraphBuilder.composable(
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
deepLinks: List<NavDeepLink> = emptyList(),
noinline enterTransition:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
EnterTransition?)? =
null,
noinline exitTransition:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
ExitTransition?)? =
null,
noinline popEnterTransition:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
EnterTransition?)? =
enterTransition,
noinline popExitTransition:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
ExitTransition?)? =
exitTransition,
noinline sizeTransform:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
SizeTransform?)? =
null,
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
) {
```
I'm wondering why there isn't an extension that accepts `route: KClass<*>` as a parameter. Something like this:
```
public fun NavGraphBuilder.composable(
route: KClass<*>,
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
deepLinks: List<NavDeepLink> = emptyList(),
enterTransition:
(@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
exitTransition:
(@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
popEnterTransition:
(@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = enterTransition,
popExitTransition:
(@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = exitTransition,
sizeTransform:
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> @JvmSuppressWildcards
SizeTransform?)? = null,
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) {
destination(
ComposeNavigatorDestinationBuilder(
provider[ComposeNavigator::class],
route,
typeMap,
content
)
.apply {
deepLinks.forEach { deepLink -> deepLink(deepLink) }
this.enterTransition = enterTransition
this.exitTransition = exitTransition
this.popEnterTransition = popEnterTransition
this.popExitTransition = popExitTransition
this.sizeTransform = sizeTransform
}
)
}
```
Reified version has the limitation in that the exact type has to be declared at call-site, which in turn makes it impossible to achieve something like this:
```
@Serializable
data object ScreenDestinationA
@Serializable
data object ScreenDestinationB
@Serializable
data object ScreenDestinationC
internal sealed class Screen(val routeClass: KClass<*>) {
data object ScreenA : Screen(ScreenDestinationA::class)
data object ScreenB : Screen(ScreenDestinationB::class)
data object ScreenC : Screen(ScreenDestinationC::class)
}
private fun NavGraphBuilder.screenA(navController: NavHostController) {
composable(route = Screen.ScreenA.routeClass) {
// ...
}
}
````
The whole idea above is to have `Screen*Destination` classes centralized somewhere and not referenced across the whole project, but the library does not allow to declare screens like that.
I'm able to solve it currently by having the proposed extension in my code, but, in my opinion, it's worth discussing if something like that should live inside the library or not.
Thanks.