Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-compose-material3

BottomNavigation items throw IllegalStateException when clicked


When I click on any of my BottomNavigation items I get an IllegalStateException.

This is my initial code that I expected to work

    BottomNavigation(contentColor = colorResource(id = R.color.white)) {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route
    menuItems.forEach {
        BottomNavigationItem(
            label = {Text(text=it.title)},
            alwaysShowLabel = true,
            selectedContentColor = Color.White,
            unselectedContentColor = Color.Gray,
            selected = currentRoute == it.route,
            onClick = {
                      navController.navigate(it.route) {
                          navController.graph.startDestinationRoute?.let {
                              route ->
                              popUpTo(route) {
                                  saveState = true
                              }
                          }
                          launchSingleTop = true
                          restoreState = true
                      }
            },
            icon= {Icon(imageVector = it.icon, contentDescription = it.title)}
        )
    }
}

I tried different variations of the code from the web but they all throw the same exception

Also tried setting alwaysShowLabel to false


Solution

  • This is the proper way to implement BottomNavigation in jetpack compose.

    1.Create a sealed class with all the screen as objects

    sealed class BottomNavClass (
        val route : String,
        val title : String,
        val icon : ImageVector
    ){
        object Home : BottomNavClass(
            route = "home",
            title = "Home",
            icon = Icons.Default.Home
        )
        object Profile : BottomNavClass(
            route = "profile",
            title = "Profile",
            icon = Icons.Default.Person
        )
        object Saved : BottomNavClass(
            route = "saved",
            title = "Saved",
            icon = Icons.Default.Favorite
        )
    
    }
    
    1. Create a NavGraph function containing of the composable function assigning the routes to screen
    @Composable
    fun BottomNavGraph(navController: NavHostController) {
    NavHost(navController = navController, startDestination = BottomNavClass.Home.route){
            composable(route = BottomNavClass.Home.route){
               
            }
            composable(route=BottomNavClass.Settings.route){
            }
            composable(route = BottomNavClass.Profile.route)
            ){
                
             
            }
          }
    
    1. An extension function for the BottomNavigation composable for defining the color and navigations.
    
    @Composable
    fun RowScope.AddItem(
        screen : BottomNavClass,
        currentDestination : NavDestination?,
        navController: NavHostController
    ) {
        BottomNavigationItem(label = {
            Text(text = screen.title)
        },
            icon = {
                Icon(imageVector = screen.icon, contentDescription = "")
            },
            selected = currentDestination?.hierarchy?.any{
                it.route == screen.route
            } == true,
            onClick = {
                navController.navigate(screen.route){
                    popUpTo(navController.graph.findStartDestination().id)
                    launchSingleTop = true
                }
            },
            unselectedContentColor = LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
            )
    }
    
    1. A composable for the BottomNavigationBar
    @Composable
    fun BottomBar(navController: NavHostController) {
    
        val screens = listOf(
            BottomNavClass.Home,
            BottomNavClass.Saved,
            BottomNavClass.Profile
        )
    
        val navBackStackEntry by navController.currentBackStackEntryAsState()
    
        val currentDestination = navBackStackEntry?.destination
    
        BottomNavigation {
            screens.forEach{
                AddItem(screen = it, currentDestination = currentDestination, navController = navController)
            }
        }
    }
    
    1. And at last placing the BottomBar in your main screen using the scaffold.
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun MainScreen() {
    
        val navController = rememberNavController()
    
        Scaffold(
            bottomBar = { BottomBar(navController = navController)}
        ) {
            Surface(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(it)
            ) {
                BottomNavGraph(navController = navController)
            }
        }
    
    }