Problem:
I have an issue with the hierarchy of my Compose Navigation. The version of compose-navigation I am using is: androidx.navigation:navigation-compose:2.7.7
.
Background:
I am trying to implement simple navigation with bottom navigation and three tabs. Each tab hosts its own nested navigation, consisting of two screens: the main tab content and a details screen, which is reused across nested graphs.
Main Activity with NavHost for the Tabs:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
TestNavigationTheme {
val tabsController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by tabsController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
BottomTabs.items.forEach { navigationItem ->
NavigationBarItem(
label = { Text(navigationItem.title) },
selected = currentDestination
?.hierarchy
?.any { it.route == navigationItem.route } == true,
icon = { Icon(navigationItem.icon, navigationItem.title) },
onClick = {
tabsController.navigate(navigationItem.route) {
popUpTo(tabsController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { paddingValues ->
NavHost(
navController = tabsController,
startDestination = BottomTabs.Feed.route,
modifier = Modifier.padding(paddingValues = paddingValues)
) {
navigation(
route = BottomTabs.Feed.route,
startDestination = "feed"
) {
composable(route = "feed") {
FeedScreen {
tabsController.navigate("details/feed")
}
}
details()
}
navigation(
route = BottomTabs.Search.route,
startDestination = "search"
) {
composable(route = "search") {
SearchScreen {
tabsController.navigate("details/search")
}
}
details()
}
navigation(
route = BottomTabs.Favourites.route,
startDestination = "fav"
) {
composable(route = "fav") {
FavouritesScreen {
tabsController.navigate("details/fav")
}
}
details()
}
}
}
}
}
}
private fun NavGraphBuilder.details() {
composable(
route = "details/{argument}",
arguments = listOf(
navArgument("argument") { type = NavType.StringType }
)
) { backStackEntry ->
DetailsScreen(
argument = backStackEntry
.arguments
?.getString("argument")
?: "Couldn't obtain the argument"
)
}
}
}
BottomTabs:
sealed class BottomTabs(val route: String, val title: String, val icon: ImageVector) {
data object Feed : BottomTabs("feed-tab", "Feed", Icons.Default.Home)
data object Search : BottomTabs("search-tab", "Search", Icons.Default.Search)
data object Favourites : BottomTabs("fav-tab", "Favourites", Icons.Default.Favorite)
companion object {
val items = listOf(Feed, Search, Favourites)
}
}
Tab Screens:
@Composable
fun FeedScreen(navigateToDetails: () -> Unit) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("Hi, I'm Feed screen")
Spacer(modifier = Modifier.size(40.dp))
Button(onClick = navigateToDetails) {
Text(text = "Go to details")
}
}
}
@Composable
fun SearchScreen(navigateToDetails: () -> Unit) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("Hi, I'm Search screen")
Spacer(modifier = Modifier.size(40.dp))
Button(onClick = navigateToDetails) {
Text(text = "Go to details")
}
}
}
@Composable
fun FavouritesScreen(navigateToDetails: () -> Unit) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("Hi, I'm Favourites screen")
Spacer(modifier = Modifier.size(40.dp))
Button(onClick = navigateToDetails) {
Text(text = "Go to details")
}
}
}
Details Screen:
@Composable
fun DetailsScreen(argument: String) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("I'm details screen")
Text("Your argument is: $argument")
}
}
Issue:
You can find this code inside NavigationBarItem, which I took from Google's guide to navigation:
selected = currentDestination
?.hierarchy
?.any { it.route == navigationItem.route } == true
When I switch to the third tab ("fav-tab") and press a button to open the details screen, the details screen opens correctly within the fav-tab. However, the navigation bar highlights the feed-tab as selected instead of the fav-tab.
What have I tried:
I tried debugging the navigation recomposition cycle by placing a breakpoint in NavigationBarItem. Here is the hierarchy:
I don't understand why the feed-tab
appears as the root of the hierarchy. In my opinion, it should be:
"details/{argument}"
"fav-tab"
because I navigated to the details screen from the fav-tab
, not the feed-tab
.
What am I doing wrong in my implementation? Any thoughts or advice would be greatly appreciated!