Search code examples
androidandroid-navigationandroid-jetpack-composeandroid-bottomnav

Jetpack Compose - Bottom navigation icon is not selected if it has nested navigation


I want to have a bottom navigation bar with two items/screens: Order and Account. Order is the start destination. Order has its own navigation and it has two screens: ItemList and ItemDetail. ItemDetail opens when an item is clicked in ItemList screen.

When I run the app, I can see the ItemList screen but Order item in the bottom navigation bar is not selected. If I click on Account item, I can see Account screen and Account item gets selected in the bottom navigation bar.

I think this is happening because of the recomposition: when Order is selected at the beginning since it is the start destination, its nested graph is called and a new destination (ItemList) is navigated, leading a recomposition, with currentRoute being "itemList" rather than "order".

How can I get Order icon selected in the bottom navigation bar? Is there a recommended what of handling nested graphs with bottom nav?

This is what I have at the moment:

object Destinations {
    const val ORDER_ROUTE = "order"
    const val ACCOUNT_ROUTE = "account"
    const val ITEM_LIST_ROUTE = "itemList"
    const val ITEM_DETAIL_ROUTE = "itemDetail"
    const val ITEM_DETAIL_ID_KEY = "itemId"
}

class NavigationActions(navController: NavHostController) {
    val selectItem: (Long) -> Unit = { itemId: Long ->
        navController.navigate("${Destinations.ITEM_DETAIL_ROUTE}/$itemId")
    }
    val upPress: () -> Unit = {
        navController.navigateUp()
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Compose
fun MyApp() {
    MyAppTheme {
        val navController = rememberNavController()

        val tabs = listOf(Destinations.ORDER_ROUTE, Destinations.ACCOUNT_ROUTE)

        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)

        Scaffold(
            bottomBar = {
                BottomNavigation {
                    tabs.forEach { tab ->
                        BottomNavigationItem(
                            icon = { Icons.Filled.Favorite },
                            label = { Text(tab) },
                            selected = currentRoute == tab,
                            onClick = {
                                navController.navigate(tab) {
                                    popUpTo = navController.graph.startDestination
                                    launchSingleTop = true
                                }
                            },
                            alwaysShowLabel = true,
                            selectedContentColor = MaterialTheme.colors.secondary,
                            unselectedContentColor = LocalContentColor.current
                        )
                    }
                }
            }
        ) {
            NavGraph(navController)
        }
    }
}

@Composable
fun NavGraph(
    navController: NavHostController,
    startDestination: String = Destinations.ORDER_ROUTE
) {
    val actions = remember(navController) { NavigationActions(navController) }

    NavHost(navController = navController, startDestination = startDestination) {
        navigation(startDestination = Destinations.ITEM_LIST_ROUTE, route = Destinations.ORDER_ROUTE) {
            composable(Destinations.ITEM_LIST_ROUTE) {
                ItemList(actions.selectItem)
            }
            composable(
                "${Destinations.ITEM_DETAIL_ROUTE}/{$Destinations.ITEM_DETAIL_ID_KEY}",
                arguments = listOf(navArgument(Destinations.ITEM_DETAIL_ID_KEY) {
                    type = NavType.LongType
                })
            ) {
                ItemDetail()
            }
        }
        composable(Destinations.ACCOUNT_ROUTE) {
            Account()
        }
    }
}

Solution

  • I wrote this article with a similar example. It's in Portuguese but if you translate the page to English you'll get the idea... Also, you can find the sources here.

    I think the problem is happening because you're using just one NavHost for the entire app. In fact, I guess you need to use one NavHost for each tab, then when the user select a tab, you must change the current NavHost.

    oh! my article is based on this post here, which can also help you.