Overview
Hello all, hope you are doing well
Since Android has come up with compose and you all want to try it so today we will take a look at bottom navigation in jetpack compose.
Note: If you are not familiar with Compose, review the Jetpack Compose resources before continuing.
Things you know before implementing bottom navigation:
- Make sure that you are on stable compose version dependency.
- Laptop/PC must have Android Studio Arctic Fox or Bumblebee
File Structure
Create files as shown below. You can also do it in the same file but for simplicity, I am keeping it separate.
So now let’s understand the implementation of compose stuff
Step 1
Add the following dependency to give support of bottom navigation in your app level gradle file (build.gradle module).
dependencies { implementation "androidx.navigation:navigation-compose:2.4.0-alpha07" }
You might wonder what’s the use of this dependency. So let us be clear first about that when you add a dependency to your project, you will have 2 main features about bottom navigation. First, users can navigate from one composable to another composable easily in the app and second, whenever the user taps the back button, in-app backStack will automatically handle it. We don’t need to manually handle backStack. The only thing that we have to handle is what to keep in backStack. One more thing at the time I am writing this blog 2.4.0-alpha07 is the latest version of the compose navigation library for better performance and more features.
Step 2
In step 2 we are going to create a sealed class. Firstly create a constant file and name it AppConstant.kt where all our app constants go. Now create one sealed class in a separate file where you have to put 3 parameters:
sealed class Screens(val route: String, var label: String, val icon: ImageVector) { object Home : Screens(ROUTE_HOME, "Home", Icons.Default.Home) object Notification : Screens(ROUTE_NOTIFICATION, "Notification", Icons.Default.Notifications) object Favorite : Screens(ROUTE_FAVORITE, "Favorite", Icons.Default.Favorite) object Setting : Screens(ROUTE_SETTING, "Setting", Icons.Default.Settings) }
Here I have kept routes in a constant file because all constants are the statics. (you can also give hard coded string but keep in mind that each routes that you are using in sealed class must be same in whole application)
Now let’s understand each parameter
- route – now you may be wondering what is the route here. In simple words route is a kind of id for each destination where you are going to navigate and each route must be unique
- label – using label you can put text which displays in bottom navigation
- icon – where you can pass an icon of your choice
Now create objects for separate screens. Here I am creating 4 screens Home, Notification, Favorite, and Setting, and pass appropriate parameters on it
Step 3
Create one separate file named navigationController.kt where we are going to implement the main part of bottom navigation to navigate through the app screens.
@Composable fun ScreenController( navController: NavHostController ) { NavHost(navController = navController, startDestination = ROUTE_HOME) { composable(ROUTE_HOME) { Home() } composable(ROUTE_NOTIFICATION) { Notification() } composable(ROUTE_FAVORITE) { Favorite() } composable(ROUTE_SETTING) { Settings() } } }
Let’s understand this one so firstly we created a composable function named ScreenController() and its params named navController of type NavHostController. Let’s see one by one.
- What is a NavHostController? NavHostController is a replacement for the XML we used to write for navigation in the old view system (navigation graph in xml)
- What is NavHost? Provides in place in the Compose hierarchy for self-contained navigation to occur
- NavHost has 5 params that are
- navController – the navController for this host which handles navigation events
- startDestination – the route for the start destination means from what you want as default screen at the time of app launch
- modifier – the modifier to be applied to the layout
- route – the route for the graph (each must be unique)
- builder – the builder used to construct the graph
- NavHost has 5 params that are
Now it’s time to create composable functions. Here I have created 4 different composable Home(), Notification(), Favorite() and Setting() and put simple TextView in each of them.
- Home()
@Composable fun Home() { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text( text = "Home", fontSize = 20.sp) } }
- Notification()
@Composable fun Notification() { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text( text = "Notification", fontSize = 20.sp) } }
- Favorite()
@Composable fun Favorite() { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text( text = "Favorite", fontSize = 20.sp ) } }
- Setting()
@Composable fun Settings() { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text( text = "Settings", fontSize = 20.sp ) } }
Yupiii! We are about to finish. Let’s keep going. You are doing great.
Step 4
So this is our last step in implementing bottom navigation. If you have any questions, please feel free to drop a comment. Ok let’s see our last step for creating UI for the bottom navigation.
@Composable fun NavigationController() { val navController = rememberNavController() @Composable fun NavigationController() { Scaffold( topBar = { TopAppBar( backgroundColor = MaterialTheme.colors.background, elevation = 10.dp ) { Row( modifier = Modifier .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text(text = "Bottom Navigation Demo") } }, bottomBar = { BottomNavigation( backgroundColor = MaterialTheme.colors.background, elevation = 16.dp ) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route items.forEach { BottomNavigationItem( icon = { Icon(imageVector = it.icon, contentDescription = "icon can't display", tint = if (currentRoute == it.route) Color.DarkGray else Color.LightGray) }, selected = currentRoute == it.route, label = { Text( text = it.label, color = if (currentRoute == it.route) Color.DarkGray else Color.LightGray, textAlign = TextAlign.Center, fontSize = 10.sp ) }, onClick = { if (currentRoute != it.route) { navController.graph.startDestinationRoute?.let { item -> navController.popBackStack( item, false ) } } if (currentRoute != it.route) { navController.navigate(it.route){ launchSingleTop = true } } }, alwaysShowLabel = false, selectedContentColor = MaterialTheme.colors.secondary, ) } } } } ) { ScreenController(navController = navController) } } }
Here val navController = rememberNavController() is responsible to navigate between this composable. The next thing is Scaffold()
Scaffold is a built-in composable function that handles positions of most commonly used concepts like TopAppBar, BottomNavigation, NavigationDrawer, FabButton, etc. When you use Scaffold composable you don’t have to specify the position for components. It will set the positions as you give things in its lambda params.
- TopAppBar: TopBar is a place where you can display text, icons as you need. As we are using Scaffold() so we are eligible for its benefits. So I have written it in the topBar lambda. It will always be on top of the screen. Here I am displaying the app name in TopAppBar and have 5 params
- modifier – The Modifier if you need to add or change padding, size,fillMaxHeight or fillMaxWeight etc
- backgroundColor – The background color to set color of TopAppBar background
- contentColor – The contentColor is where we can change content color like text Color
- elevation – The elevation of TopAppBar
- contentPadding – the contentPadding applied to the content of this TopAppBar
- content – The content of this TopAppBar. The default layout here is a Row, so content inside will be placed horizontally
- BottomNavigation: It is similar to TopBar but the difference is it appears at the bottom of the screen. All params are the same as TopBar and you can see I have written in bottomBar lambda so it will always appear at the bottom of the screen. Here in bottomBar we utilize sealed class paras Routes, Label, and Icon.
In BottomNavigation we used two params first is backgroundColor = MaterialTheme.colors.backgroundelevation = 16.dp to set background color and we are using the theme default background color which is nothing but White and second elevation = 16.dp to give elevation in bottom navigation.
Now we have to declare 2 variables named navBackStackEntry and currentRoute. Let’s understand what these two variables do- navBackStackEntry: on navController we are calling currentbackEntryAsState() means whenever backStack changes, the composable function will recompose itself
- currentRoute: we fetch the current route from navBackStackEntry and store the current route
After this we iterate over items to create BottomNavigation so above code you can see we have done something like items.forEach {…} before implementing this. Go to the sealed class which we created earlier and create an object of type list and give it the same values as you created in sealed objects.
object Items { val items = listOf( Home, Notification, Favorite, Setting ) }
Note: Remember name must be the same as you declare object name of your sealed class.
So we have defined items.forEach {…}. Now we create our BottomNavigation UI to implementing BottomNavigation and we have composable function named BottomNavigationItem() which has 10 params so let’s understand each one
- selected – Whether this item is selected
- onClick – The callback to be invoked when this item is selected
- icon – This will be an Icon of your bottom navigation
- modifier – Optional Modifier for item
- enabled – Controls the enabled state of this item. When false, this item will not be clickable
- label – Optional text label for text to be displayed in bottom navigation
- alwaysShowLabel – Whether to always show the label for this item. If false, the label will only be shown when this item is selected
- interactionSource – You can create and pass in your own remembered MutableInteractionSource if you want to observe Interactions and customize the appearance / behavior of this BottomNavigationItem in different Interactions
- selectedContentColor – Changes the color of the text label and icon when item is selected, and the color of the ripple
- unselectedContentColor – Changes the color of the text label and icon when an item is not selected
We have understood all params and its use so let’s see what we have done.
In BottomNavigationItem() we are using size parameters as follow:
- icon = {…} – In the icon lambda we are calling Icon(..) composable where we are passing three params.
In imageVector we had written it.icon where it is the instance of screen.
In imageVector we had written it.icon where it is the instance of screen sealed class icons param. contentDescription when the icon can’t display due to some error then text will appear instead of the icon. And tint for changing the color of the icon based on condition - label = {…} – In the label lambda we are calling Text(..) composable where we pass params like text for displaying labels and it.label which is an instance of sealed class label param.color for setting text color based on condition. fontSize indicates the size of fonts of bottom navigation labels we have defined
- selected – is Boolean param that represents the current selected tab to be displayed on screen where it is an instance of screen sealed class route param
- onClick = {…} – is lambda that is responsible for action when user tabs on bottomNavigation. Here we are managing backStack entries. We are checking the condition if currentRoute != it.route means if both values are not same then control goes inside the condition and here comes interesting part, now on every tabs of user we are doing popBackStack till the startDestination but not taking startDestination which means each time when user tabs popBackStack is called and removes composable’s from stack accept we define in startDestination which is ROUTE_HOME
- Inside onClick we had also done two more thing that is
if (currentRoute != it.route) { navController.navigate(it.route){ launchSingleTop = true } }
Here we have conditions same as first in onClick but apart from that another thing is that we had to call navController.navigate(it.route) Which means when the user clicks the same on the same table then nothing should happen. And launchSingleTop = true is to Avoid multiple copies of the same destination when reselecting the same item. And finally called ScreenController(navController = navController) by passing navController in the content part of Scaffold. That’s it. We have successfully Implemented BottomNavigation in Jetpack Compose.
Preview
Conclusion
As we have seen, bottom navigation in jetpack compose with interesting and powerful features for development. For more in-depth knowledge check out official documentation Bottom Navigation in Jetpack Compose
Thanks for reading. To help others please click ❤ to recommend this article if you found it helpful. Happy Coding