MR3Y
07/05/2024, 1:08 PMval isSelected = currentDestination?.hierarchy?.any { it.route == tab.route } == true
but the problem is that it only works for top level destinations in my hierarchy (tab A, tab B, tab C). I think it is easy but I can't figure it outStylianos Gakis
07/05/2024, 1:49 PMNavHost(route = "Root") {
navigation("Tab A", startDestination = "Tab A screen") {
composable("Tab A screen") {}
composabel("NestedScreen") {}
}
navigation("Tab B") {}
navigation("Tab C") {}
}
And so on.
hierarchy
looks up the graph, so it needs to find Tab A
up the graph to match it.
In `NestedScreen`'s hierarchy going up, it will first find "Tab A" and then "Root".Stylianos Gakis
07/05/2024, 1:50 PMNavHost(route = "Root") {
navigation("Tab A", startDestination = "Tab A screen") {
composable("Tab A screen") {}
}
navigation("Tab B") {}
navigation("Tab C") {}
composable("NestedScreen") {}
}
Since for "NestedGraph", the tab you are trying to find in the hierarchy just isn't there. The only thing it will find going up the hierarchy is the "Root" route, the one your NavHost has.MR3Y
07/05/2024, 2:24 PMNavHost(route = "Root") {
composable("Tab A") {}
composable("Tab B") {}
composable("Tab C") {}
composable("NestedScreen") {}
}
I'm trying to use your proposed solution but the problem is I can navigate to NestedScreen
from tab A and tab B and I want to make whatever tab I navigate from is the selected one. I think I didn't clarify that in my original questionMR3Y
07/05/2024, 2:24 PMStylianos Gakis
07/05/2024, 2:42 PMStylianos Gakis
07/05/2024, 2:42 PMIan Lake
07/05/2024, 2:43 PMIan Lake
07/05/2024, 2:44 PMStylianos Gakis
07/05/2024, 2:45 PMNavHost(route = "Root") {
navigation("Tab A", startDestination = "Tab A screen") {
composable("NestedScreenA") { SameComposableForNestedScreen() }
}
navigation("Tab B") {
composable("NestedScreenB") { SameComposableForNestedScreen() }
}
navigation("Tab C") {
composable("NestedScreenC") { SameComposableForNestedScreen() }
}
}
Is an easy way to do it. If that is what you want of course and you still want to rely on hierarchy to grab the selected item.
Then depending on which tab you are in, navigate to the right route accordingly.
If you want to have a deep link to them however, and you do not want the deep link to always end up in the "A" version of it or something like that, it can get a bit more involved. If you do not have a deep link this honestly works perfectly fine.MR3Y
07/05/2024, 3:51 PM@Composable
fun Root(modifier: Modifier = Modifier) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val topLevelDestinations = listOf(
Tab(
"Tab A",
Icons.Outlined.Home,
createRoutePattern<NavigationDestination.A>(),
NavigationDestination.A,
),
Tab(
"Tab B",
Icons.Outlined.Add,
createRoutePattern<NavigationDestination.B>(),
NavigationDestination.B,
),
Tab(
"Tab C",
Icons.Outlined.Settings,
createRoutePattern<NavigationDestination.C>(),
NavigationDestination.C,
),
)
Column(modifier = modifier) {
Box(modifier = Modifier.weight(1f)) {
NavHost(
navController = navController,
startDestination = "Tab B",
modifier = Modifier.fillMaxSize(),
route = "Root"
) {
navigation(
startDestination = createRoutePattern<NavigationDestination.A>(),
route = "Tab A"
) {
composable<NavigationDestination.A> {
Box(
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Screen A")
Button(onClick = { navController.navigate(NavigationDestination.Nested) }) {
Text(text = "Navigate to Nested screen")
}
}
}
}
composable<NavigationDestination.Nested> {
Box(
contentAlignment = Alignment.Center
) {
Text(text = "Screen Nested")
}
}
}
navigation(
startDestination = createRoutePattern<NavigationDestination.B>(),
route = "Tab B"
) {
composable<NavigationDestination.B> {
Box(
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Screen B")
Button(onClick = { navController.navigate(NavigationDestination.Nested) }) {
Text(text = "Navigate to Nested screen")
}
}
}
}
}
navigation(
startDestination = createRoutePattern<NavigationDestination.C>(),
route = "Tab C"
) {
composable<NavigationDestination.C> {
Box(
contentAlignment = Alignment.Center
) {
Text(text = "Screen C")
}
}
}
}
}
NavigationBar(
modifier = Modifier
.fillMaxWidth()
) {
topLevelDestinations.forEach { tab ->
val isSelected = currentDestination?.hierarchy?.any { it.route == tab.route } == true
NavigationBarItem(
selected = isSelected,
onClick = {
if (!isSelected) {
navController.navigate(tab.destination) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
},
label = { Text(text = tab.label) },
icon = { Icon(tab.icon, contentDescription = null) }
)
}
}
}
}
data class Tab(
val label: String,
val icon: ImageVector,
val route: String,
val destination: NavigationDestination,
)
and destinations are (I'm using kiwi navigation compose typed):
sealed interface NavigationDestination : Destination {
@Serializable
data object A : NavigationDestination
@Serializable
data object B : NavigationDestination
@Serializable
data object C : NavigationDestination
@Serializable
data object Nested : NavigationDestination
}
Stylianos Gakis
07/05/2024, 4:03 PMval isSelected = currentDestination?.hierarchy?.any { it.route == tab.route } == true
but tab.route
is createRoutePattern<NavigationDestination.A>()
.
The sub-nav-graph route is not that, it's "Tab A"
, so you need to compare it with that instead.Stylianos Gakis
07/05/2024, 4:07 PMsealed interface NavigationDestination : Destination {
@Serializable
data object AGraph : NavigationDestination
@Serializable
data object BGraph : NavigationDestination
@Serializable
data object CGraph : NavigationDestination
@Serializable
data object A : NavigationDestination
@Serializable
data object B : NavigationDestination
@Serializable
data object C : NavigationDestination
@Serializable
data object Nested : NavigationDestination
}
and then use the type-safety for the navgraph too
navigation<NavigationDestination.AGraph>(
startDestination = createRoutePattern<NavigationDestination.A>(),
) {
And probably
val topLevelDestinations = listOf(
Tab(
"Tab A",
Icons.Outlined.Home,
createRoutePattern<NavigationDestination.AGraph>(),
NavigationDestination.AGraph,
),
Tab(
"Tab B",
Icons.Outlined.Add,
createRoutePattern<NavigationDestination.BGraph>(),
NavigationDestination.BGraph,
),
Tab(
"Tab C",
Icons.Outlined.Settings,
createRoutePattern<NavigationDestination.CGraph>(),
NavigationDestination.CGraph,
),
)
so that you do not get confuse about what is what.
tl;dr:
You need to differentiate between what's the graph's route and the destination's route and use the right one, you are trying to match the composable<> route here and not the one of the navigation<>MR3Y
07/05/2024, 4:09 PMMR3Y
07/05/2024, 4:10 PMStylianos Gakis
07/05/2024, 4:21 PMenum class TopLevelGraph {
Home,
Insurances,
Payments,
Profile,
}
And at the point where we want to check we just exhaustively go over this enum, https://github.com/HedvigInsurance/android/blob/afb510b3ebdf48ebc9e9597d31fc5de13d[…]/kotlin/com/hedvig/android/app/ui/IsTopLevelGraphInHierarchy.kt but we then map manually to each destination, which again, is not part of some sealed hierarchy for this. Same for when we want to navigate to them https://github.com/HedvigInsurance/android/blob/876c462a2e774d74637bbf18695017b785[…]app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt.
Also with the way you got it now, since you got the sealed interface itself be a Destination
inheritor, you can write
navigation<NavigationDestination> {}
And that would not complain when you write it, but it is not what you want at all.
Since you're also using the kiwi library (like we do too atm) I'd recommend just making the destinations just inherit from both : NavigationDestination, Destination
to avoid this problem.MR3Y
07/05/2024, 4:26 PM