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.
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.