You're reading for free via Jayant Kumar🇮🇳's Friend Link. Become a member to access the best of Medium.

Member-only story

Bottom Navigation Bar in Jetpack Compose.

Jayant Kumar🇮🇳
5 min readAug 31, 2023
Photo by Anastasia Petrova on Unsplash

Link For Non-Members

In this article we will be talking about Bottom Navigation Bar in Jetpack Compose.

Basically we will have three screens …..

Splash → BottomBar (Home , Notification , Profile)-> Detail

First screen will be your splash screen after 2 secs it will move to bottom bar screen , when we will click on move to detail screen button it will move to detail screen and we will also see how to hide bottom Bar when it’s not needed , Interesting!!

First let’s define all the required routes/tags (unique keys) for all the composable functions.

BottomBar Routes

enum class BottomBarRoutes(
val id: Int,
@StringRes val title: Int,
val routes: String,
@DrawableRes val icon: Int
) {

HOME(1, R.string.home, "/home", R.drawable.home),
NOTIFICATION(
2,
R.string.notification, "/notification", R.drawable.notification
),
Profile(3, R.string.profile, "/profile", R.drawable.profile)

}

As you can see in the above code , we have created an enum class , which contains Bottom Bar’s routes , title & icon .

Normal Screen Routes

sealed class ScreenRoutes(val route: String) {

data object Splash : ScreenRoutes("/splash")
data object BottomBar : ScreenRoutes("/bottombar")
data object Detail : ScreenRoutes("/detail")

}

As you can see above , we have created routes for splash , bottombar & detail screen. bottombar object will be your nested navigation , which contains BottomBarRoutes enum inside it (if you didn’t get don’t worry).

Okay so we defined all the routes now let’s create all the composable functions ( splash, bottombar (Home , Notification , Profile) , detail screens ).

Splash Screen

@Composable
fun SplashScreen(
navHostController: NavHostController
)
{

LaunchedEffect(key1 = Unit){
delay(2000)
navHostController.navigate(ScreenRoutes.BottomBar.route)
}

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Text(text = "Splash Screen", style = TextStyle(
color = Color.Black,
fontWeight = FontWeight.SemiBold,
fontSize = 30.sp
)
)
}

}

In the splash screen composable function , we are navigating to BottomBar screen after 2 secs with help of LaunchEffect composable function.

Home Screen

@Composable
fun HomeScreen(
navHostController: NavHostController,
context: Activity
)
{

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = {
navHostController.navigate(ScreenRoutes.Detail.route)
}) {
Text(text = stringResource(R.string.move_to_detail_screen))
}
}

BackHandler {
context.finish()
}

}

Notification Screen

@Composable
fun NotificationScreen(
navHostController: NavHostController
)
{

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Text(text = "Notification Screen", style = TextStyle(
color = Color.Black,
fontWeight = FontWeight.SemiBold,
fontSize = 30.sp
)
)
}
BackHandler {
navHostController.navigate(BottomBarRoutes.HOME.routes){
popUpTo(BottomBarRoutes.HOME.routes){
inclusive = true
}
}
}
}

Here In the above code , we have BackHandler composable function , which will handle back press ( like whenever we press back button in the bottombar screen , it will move to home screen).

Profile Screen

@Composable
fun ProfileScreen(
navHostController: NavHostController
)
{

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Text(text = "Profile Screen", style = TextStyle(
color = Color.Black,
fontWeight = FontWeight.SemiBold,
fontSize = 30.sp
)
)
}

BackHandler {
navHostController.navigate(BottomBarRoutes.HOME.routes){
popUpTo(BottomBarRoutes.HOME.routes){
inclusive = true
}
}
}

}

Detail Screen

@Composable
fun DetailScreen(
navHostController: NavHostController
)
{

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Text(text = "Detail Screen", style = TextStyle(
color = Color.Black,
fontWeight = FontWeight.SemiBold,
fontSize = 30.sp
)
)
}

}

That’s it , Now it’s time to define Navigation graph , where we will keep all the composable function for the navigation.

Navigation Screen

@Composable
fun BottomBarNavigation(
navHostController: NavHostController,
padding: PaddingValues,
context:Activity
) {

NavHost(
navController = navHostController, startDestination = ScreenRoutes.Splash.route,
modifier = Modifier.padding(padding)
) {
composable(ScreenRoutes.Splash.route) {
SplashScreen(navHostController = navHostController)
}
navigation(
route = ScreenRoutes.BottomBar.route,
startDestination = BottomBarRoutes.HOME.routes
) {
composable(BottomBarRoutes.HOME.routes) {
HomeScreen(navHostController = navHostController,context)
}
composable(BottomBarRoutes.NOTIFICATION.routes) {
NotificationScreen(navHostController = navHostController)
}
composable(BottomBarRoutes.Profile.routes) {
ProfileScreen(navHostController = navHostController)
}
}
composable(ScreenRoutes.Detail.route) {
DetailScreen(navHostController = navHostController)
}
}

}

In the above code , we have created NavHost and inside it we passed all the composable functions. Splash & Detail screen will be in the composable function but bottombar will be in your navigation function (Nested Navigation). and navigation function also contains route (through that we will identify the bottombar screen) and startDestination will be your first screen when we reached to bottombar screen. Hope you got it.

Now Create BottomBar section

@Composable
fun BottomBarRow(
navHostController: NavHostController
)
{

val tabList = listOf(
BottomBarRoutes.HOME,
BottomBarRoutes.NOTIFICATION,
BottomBarRoutes.Profile
)

val navStackBackEntry by navHostController.currentBackStackEntryAsState()
val currentDestination = navStackBackEntry?.destination


Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically
) {
tabList.forEach { tab ->
BottomBarItems(
tab = tab,
currentDestination = currentDestination,
navHostController = navHostController
)
}
}

}

@Composable
fun BottomBarItems(
tab: BottomBarRoutes,
currentDestination: NavDestination?,
navHostController: NavHostController
)
{

val selected = currentDestination?.hierarchy?.any { it.route == tab.routes } == true

val contentColor =
if (selected) Color.Unspecified else MaterialTheme.colorScheme.onPrimary

IconButton(onClick = {
navHostController.navigate(tab.routes)
}) {
Icon(
painter = painterResource(id = tab.icon),
contentDescription = "",
tint = contentColor,
modifier = Modifier.size(30.dp)
)
}


}

In the above code we create three tabs for our bottombar.

Now comes into the main part , hide bottombar when it's not needed !!

we don’t need to show bottombar when we in the splash & detail screen. For that we have to create states, which will keep track the current destination of navigation graph.

@Composable
fun rememberAppState(
navHostController: NavHostController = rememberNavController()

) = remember(navHostController) {
AppState(navHostController)
}

@Stable
class AppState(
val navHostController: NavHostController
) {

private val routes = BottomBarRoutes.values().map { it.routes }

val shouldShowBottomBar: Boolean
@Composable get() =
navHostController.currentBackStackEntryAsState().value?.destination?.route in routes
}

Now in the above code , we have created a AppState class which contains navHostController parameter (this object will be the same for all the composable functions).

After that we get all the routes of bottombar (Home , Notification , Profile). and check if it’s present in the current destination or not through navHostController (it will return a boolean value).

Now pass this AppState class in the rememberAppState composable function to remember all the things , like whenever screen destination change it will recompose.

Now pass navigation , bottombar & rememberAppState() in the Scaffold composable function , because through Scaffold we can show bottombar.

Goto your MainActivity …..

MainActivity

@OptIn(ExperimentalMaterial3Api::class)
class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ArticlesRepositoryTheme {
val appState = rememberAppState()
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Scaffold(
bottomBar = {
if (appState.shouldShowBottomBar)
BottomAppBar(
containerColor = MaterialTheme.colorScheme.primary,
contentPadding = PaddingValues(horizontal = 20.dp),
modifier = Modifier
.height(70.dp)
.clip(
RoundedCornerShape(
topStart = 24.dp, topEnd = 24.dp
)
)
) {
BottomBarRow(
navHostController = appState.navHostController,
)
}
}
) { innerPadding ->
BottomBarNavigation(
navHostController = appState.navHostController,
padding = innerPadding,
this
)
}
}
}
}
}
}

As you can see in the above code , first we called rememberAppState() composable function. and in the bottombar section first we check if the appState.shouldShowBottombar is true or not , if it’s true then show bottombar otherwise hide it.

And also passed BottomBarNavigation composable function in the content parameter of Scaffold for the navigation.

SOURCE CODE

That’s all for today my friends , Hope you enjoyed and learnt lots of thing :)

Jayant Kumar🇮🇳
Jayant Kumar🇮🇳

Written by Jayant Kumar🇮🇳

Hello My name is Jayant Kumar, I am a software Engineer , specialist in Mobile Development (Android , IOS , Flutter , React Native) from India 🇮🇳

Responses (1)

Is it possible to do the same thing, but use NavigationBar with NavigationBarItem instead of BottomAppBar and BottomBarRow? Thanks for the interesting tutorial.