Implementing Bottom Navigation in Jetpack Compose

I’m building a mobile app with Jetpack Compose and would like to implement bottom navigation with multiple tabs. I have a DashboardCustomerScreen that includes a bottom navigation bar with icons and titles for “Home,” “Reservation,” and “Profile.” I’m using a combination of Scaffold and custom components, and I have a working navigation setup using the Navigation component.

Here’s a snippet of the code for the DashboardCustomerScreen:

@Composable
fun DashboardCustomerScreen(profileCustomerViewModel: ProfileCustomerViewModel, dashboardViewModel: DashboardViewModel, navController: NavController) {
    // ... (other code)

    Scaffold(
        topBar = {
            // ... (top app bar)
        },
        bottomBar = {
            // ... (bottom navigation bar)
        },
        content = {
            // ... (content area)
        }
    )
}

I’m looking for guidance on refining the bottom navigation bar, handling item selection, and navigating to different destinations when a tab is selected. Are there any best practices or improvements I can make in terms of Jetpack Compose conventions?

Handle Item Selection:

Utilize the NavHost for handling navigation between different destinations. It looks like you already have a NavHost set up in the SetupNavGraph function. Ensure that each screen is properly annotated with @Composable and connected to the NavHost.

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun SetupNavGraph(
    navController: NavHostController
){
    NavHost(
        navController = navController,
        startDestination = Screen.Home.route
    ){
        composable(Screen.Home.route){
            Home(navController)
        }
        composable(Screen.DashboardCustomer.route){
            DashboardCustomerScreen(ProfileCustomerViewModel(LocalContext.current, DataManager(LocalContext.current)),
                DashboardViewModel(DataManager(LocalContext.current)), navController)
        }
    }
}

sealed class Screen(val route: String) {
    object  Home : Screen("home")
    object DashboardCustomer : Screen("dashboardcustomer")
}

this is example of how i define bottom navigation

data class BottomNavigationItem(
    val title: String,
    val selectedIcon: ImageVector,
    val unselectedIcon: ImageVector,
)

@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun DashboardCustomerScreen(profileCustomerViewModel: ProfileCustomerViewModel, dashboardViewModel: DashboardViewModel, navController: NavController) {
    val items = listOf(
        BottomNavigationItem(
            title = "Home",
            selectedIcon = Icons.Filled.Home,
            unselectedIcon = Icons.Outlined.Home,
        ),
        BottomNavigationItem(
            title = "Reservation",
            selectedIcon = Icons.Filled.StickyNote2,
            unselectedIcon = Icons.Outlined.StickyNote2
        ),
        BottomNavigationItem(
            title = "Profile",
            selectedIcon = Icons.Filled.Person,
            unselectedIcon = Icons.Outlined.Person,
        )
    )

    var selectedItemIndex by rememberSaveable { mutableStateOf(0) }
    var expanded by remember {
        mutableStateOf(false)
    }

    val isSuccessful by dashboardViewModel.isSuccessful.observeAsState()

    if(isSuccessful == true){
        navController.navigate("login")
    }

    var openDialog by remember{ mutableStateOf(false) }

    if(openDialog){
        ChangePasswordDialog(dashboardViewModel = dashboardViewModel, setShowDialog = { openDialog = it }, navController = navController)
    }

        Scaffold(
            topBar = {
                CenterAlignedTopAppBar(
                    colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                        containerColor = MaterialTheme.colorScheme.primaryContainer,
                        titleContentColor = MaterialTheme.colorScheme.primary,
                    ),
                    title = {
                        Text(
                            "",
                            maxLines = 1,
                            overflow = TextOverflow.Ellipsis
                        )
                    },
                    actions = {
                        IconButton(onClick = { expanded = true }) {
                            Icon(
                                imageVector = Icons.Filled.Menu,
                                contentDescription = "Localized description"
                            )
                        }
                        DropdownMenu(
                            expanded = expanded,
                            onDismissRequest = { expanded = false },
                        ) {
                            DropdownMenuItem(
                                text = {
                                    Text(text = "Change Password")
                                },
                                onClick = {
                                    expanded = false
                                    openDialog = true
                                }
                            )
                            DropdownMenuItem(
                                text = {
                                    Text(text = "Logout")
                                },
                                onClick = {
                                    expanded = false
                                    dashboardViewModel.logout()
                                }
                            )
                        }
                    },
                )
            },
            bottomBar = {
                NavigationBar() {
                    items.forEachIndexed { index, item ->
                        NavigationBarItem(
                            selected = selectedItemIndex == index,
                            onClick = {
                                selectedItemIndex = index
//                                when (index) {
//                                    0 -> navController.navigate("dashboard")
//                                    1 -> navController.navigate("login")
//                                }
                            },
                            label = {
                                Text(text = item.title)
                            },
                            icon = {
                                Icon(
                                    imageVector =
                                    if (selectedItemIndex == index)
                                        item.selectedIcon
                                    else item.unselectedIcon,
                                    contentDescription = item.title
                                )
                            }
                        )
                    }
                }
            },
            content = {
                    padding ->
                Row(
                    Modifier
                        .fillMaxSize()
                        .padding(padding)
                        .consumeWindowInsets(padding)
                        .windowInsetsPadding(
                            WindowInsets.safeDrawing.only(
                                WindowInsetsSides.Horizontal,
                            ),
                        ),
                ) {
                    Column(Modifier.fillMaxSize()
                        )
                    {
                        when (selectedItemIndex) {
                            0 -> ReservationScreen(
                                ReservationViewModel(
                                    DataManager(LocalContext.current)
                                )
                            )

                            1 -> ReservasiScreen(
                                ReservasiViewModel(
                                    LocalContext.current,
                                    DataManager(LocalContext.current)
                                ),
                                ReservasiUangJaminanViewModel(
                                    DataManager(LocalContext.current),
                                    LocalContext.current
                                ),
                                navController
                            )

                            2 -> ProfileCustomerScreen(profileCustomerViewModel, navController)
                        }
                    }
                }
            }
        )

}

Leave a Comment