Bottom Navigation in Jetpack Compose

January 4th, 2022

Here is a quick post providing a sample of implementing Bottom Navigation in Jetpack compose and mixing it with regular screen navigation. Other documentation that I found on this topic was pretty sparse so I figured it may be helpful for you to see it all in one small sample repo.

This is what we are trying to achieve:

Full source code can be found on Github

Look in the navigation package of that repo. You will see 3 files:

  • BottomNavigationBar.kt

  • NavigationSetup.kt

  • Screen.kt

Screen.kt defines a list of the destinations in the app which includes bottom navigation destinations and regular destinations.

sealed class Screen(val route: String) {
    object Home : Screen("home_screen")
    object Settings : Screen("settings_screen")
    object About : Screen("about_screen")
}

NavigationSetup.kt defines our nav graph:

@Composable
fun NavigationSetup(navController: NavHostController) {
    NavHost(navController, startDestination = BottomNavItem.Home.route) {
        composable(BottomNavItem.Home.route) {
            HomeScreen()
        }
        composable(BottomNavItem.Settings.route) {
            SettingsScreen(navController)
        }
        composable(Screen.About.route) {
            AboutScreen()
        }
    }
}

HomeScreen and SettingsScreen are bottom nav destinations. AboutScreen is a regular destination. User can navigate to the AboutScreen from the SettingsScreen by tapping on a button.

SettingsScreen takes navController param so it can use it to navigate to the AboutScreen

Also note that BottomNavItem.Home.route is defined as the start destination for our NavHost.

BottomNavigationBar.kt defines our bottom nav destinations and BottomNavigation config.

sealed class BottomNavItem(
    val route: String,
    @StringRes val titleResId: Int,
    val icon: ImageVector
) {
    object Home : BottomNavItem(
        route = Screen.Home.route,
        titleResId = R.string.screen_title_home,
        icon = Icons.Default.Home
    )

    object Settings : BottomNavItem(
        route = Screen.Settings.route,
        titleResId = R.string.screen_title_settings,
        icon = Icons.Default.Settings
    )
}

Note, that bottom nav set up re-uses screen configurations from such as routes from Screen.kt as much as possible to provide a Single Source of Truth in our navigation configuration.

This block is derived from the official docs found at https://developer.android.com/jetpack/compose/navigation#bottom-nav

@Composable
fun BottomNavigationBar(
    navController: NavController
) {
    val items = listOf(
        BottomNavItem.Home,
        BottomNavItem.Settings
    )

    BottomNavigation {
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route
        items.forEach { item ->
            BottomNavigationItem(
                icon = {
                    Icon(
                        imageVector = item.icon,
                        contentDescription = stringResource(id = item.titleResId)
                    )
                },
                label = { Text(text = stringResource(id = item.titleResId)) },
                selected = currentRoute == item.route,
                onClick = {
                    navController.navigate(item.route) {
                        // Pop up to the start destination of the graph to
                        // avoid building up a large stack of destinations
                        // on the back stack as users select items
                        navController.graph.startDestinationRoute?.let { route ->
                            popUpTo(route) {
                                saveState = true
                            }
                        }
                        // Avoid multiple copies of the same destination when re-selecting the same item
                        launchSingleTop = true
                        // Restore state when re-selecting a previously selected item
                        restoreState = true
                    }
                }
            )
        }
    }
}

And finally, here is how it all bootstrapped in MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBottomNavTheme {
                Surface(color = MaterialTheme.colors.background) {
                    val navController = rememberNavController()
                    Scaffold(
                        bottomBar = { BottomNavigationBar(navController) }
                    ) {
                        NavigationSetup(navController = navController)
                    }
                }
            }
        }
    }
}

Our Scaffold takes bottomBar config as well as sets up the overall navigation graph. In both cases, navController is passed down them. That’s all for now.

You can find the entire source code here.

 
Previous
Previous

Intercept back press in Jetpack Compose

Next
Next

Manage multiple Git configs