Skip to main content

Android Development: Addition of Bottom Navigation Bar with Kotlin and Jetpack Compose

 

Introduction

In this article, we will add bottom navigation bar to the second screen of the “Demo One” App that I have been using for demonstrations in my previous Android development articles. The bottom navigation bar will have three navigation items, which are, “Home”, “Info” and “Settings”.  The navigation items would be programmed to render their contents on the screen accordingly when clicked or tapped.

Note that the “Demo One” Android project already had a TopAppBar, as added in my previous article. The user interface of the second screen of the Demo One App is as shown in Figure 1.

Figure 1

Addition of Needed Dependency

The dependency that we will need for the addition of bottom navigation bar to our Android project is called “navigation”. Let us now open the module-level build.gradle file in our Android project and add the version 2.6.0 of the navigation dependency.

After the addition of the dependency, we will connect to the internet and then click on “Sync Now” as in Figure 2. We can then disconnect from the internet after successful synchronization.

Figure 2

Creation of Composable Functions to Represent Each Screen

We will now create composable functions for the screen that will show when each bottom navigation item is tapped or clicked.

So, inside SecondActivity.kt, we will add the HomeScreen, InfoScreen and SettingsScreen functions. For our demonstration, each screen will just display some texts accordingly. Since the three screens will have similar text styles, we can declare one composable function named MainTextStyle that will have “title” as its parameter and then call it in each of the screen functions (as in Figures 3 to 5).

Figure 3

Figure 4

Figure 5

Creation of Sealed Class for Navigation Items

We will now create a new Kotlin sealed class with the name “NavigationItem” (as in Figures 6 and 7). Inside it, we will define the route, label and icons for our bottom navigation items.

Note that “Sealed class” is used to create well defined hierarchies of related classes, especially when we intend to have a set of restricted expressions in our code. This makes our code more maintainable, and ensures that all cases are handled explicitly, thereby promoting safety.
Figure 6

Figure 7

NavigationItem.kt is successfully created. Let us now add some lines of code to the sealed class (as in Figure 8).
Figure 8

Note that our sealed class accepts three parameters, that is “route” of String type, “label” of String type, and “icons” of ImageVector type as its properties. In the body of our class, we declared three singleton objects (Home, Info and Settings) as subclasses, and passed in their respective route, label and icons accordingly.

Addition of a New Composable Function Named NavigationController

Inside SecondActivity.kt, we will declare a new composable function named NavigationController and update it with some lines of code.

The function will receive navController of the type NavHostController as parameter. In the body of the function, we will call NavHost, pass in navController as the value for navController, and the Home route as the value for startDestination. Note that NavHost manages the different destinations. Next, in the body of the NavHost function, we will add three composable blocks (as in Figure 9), each of which will point to the different screen destinations, for instance, “Home” route will invoke HomeScreen() function and render its content on the screen, “Info” route will invoke InfoScreen() function and render its content on the screen, and so on.

Figure 9

Declaration of navController and bottomNavItems Variables

Still inside SecondActivity.kt, above the Scaffold that is inside the SecondScreen function, we will declare a variable named navController and initialize it to rememberNavController(). Also, we will declare another variable named bottomNavItems and assign it the list of our navigation items (as in Figure 10).
Figure 10

Addition of bottomBar Component

Still inside the SecondScreen function, we will add the bottomBar component after the topBar.

NB: There is the need to press “alt+ enter” on:

navController.currentBackStackEntryAsState()

to import it.

We will then update the lines of code in bottomBar as in Figures 11 and 12:

Figure 11

Figure 12

Note that still inside the SecondScreen composable function, we have the bottomNavItems variable which contains the list of our navigation items. Using the forEach statement, we iterated through the list, and for each navigation item, we created NavigationBarItem which has "selected", "icon", "label" and "onClick" parameters.

The currentRoute is set as the value of the "selected" property to specify the current navigation item.

The label is displayed with the use of Text composable, and we made its color to be LightGray if the item is selected (that is, it is the current route) and Magenta if it is not selected.

The icon of the navigation item is displayed using the Icon composable. We made the color of the icon to be DarkGray if the navigation item is selected, and Magenta if it is not selected.

For the onClick property, we checked if the currentRoute is different from the route of the clicked navigation item (that is, "it.route"). If it is different, we popped the back stack up to the start destination route, and then navigated to the route of the clicked item with the "launchSingleTop" flag.

Next, as shown in Figure 13, inside the body of the Scaffold, we will call or create an instance of the NavigationController function and pass navController as an argument to it. Note that NavigationController manages navigation from one screen to another within an Android application.

Figure 13

Next, inside the setContent block of SecondActivity.kt, we will call SecondScreen() function (as in Figure 14).
Figure 14

Addition of BackHandler to Each Screen

To do this, we will go to the NavigationController composable function inside SecondActivity.kt, and then update the lines of code in it (as in Figures 15 to 16):

Figure 15

Figure 16

Inside the composable block for the screens, we have added BackHandler method and used “Intent” to indicate the Activity that should be launched when users press the back button on their device.

With that, when the back button is pressed from the Home Screen, the app will navigate to MainActivity, but when the back button is pressed from the info screen and Settings screen, the app will navigate to the SecondActivity, in which the Home Screen is the destination.

 

Addition of BackHandler to MainActivity

In MainActivity.kt, we will add the BackHandler and call the finishAffinity() function so as to make the app to close when the user presses the back button from MainActivity.kt (as in Figure 17).
Figure 17

Testing the App

We can now test our Demo One App by running it in our emulator.

The app worked as expected (as in Figure 18).

Figure 18

That’s it.

Thank you for reading.

Appendix: Compilation of the Lines of Code

MainActivity.kt

package com.ajirelab.demoone

import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.ajirelab.demoone.ui.theme.DemoOneTheme

class MainActivity : ComponentActivity() {
   
override fun onCreate(savedInstanceState: Bundle?) {
       
super.onCreate(savedInstanceState)
       
setContent {
           
DemoOneTheme {
               
// A surface container using the 'background' color from the theme
               
Surface(
                   
modifier = Modifier.fillMaxSize(),
                   
color = MaterialTheme.colorScheme.background
               
) {
                   
DemoScreen()

                   
BackHandler {
                      
finishAffinity()
                    }
                }
            }
        }
   
}
}

@Composable
fun DemoScreen(){
   
Box(modifier = Modifier.fillMaxSize()) {
       
Image(
           
painter = painterResource(id = R.drawable.demo_bg),
           
contentDescription = "Demo Background",
           
contentScale = ContentScale.FillBounds,
           
modifier = Modifier.matchParentSize()
        )
    }

   
Column(
       
modifier = Modifier.fillMaxSize(),
       
verticalArrangement = Arrangement.Center,
       
horizontalAlignment = Alignment.CenterHorizontally
   
) {
       
Text(text = "Welcome", fontSize = 30.sp, fontWeight = FontWeight.Bold)

       
//Alert Dialog to be show when button is clicked
       
val showAlertMessage = remember{ mutableStateOf(false) }
       
if(showAlertMessage.value){
           
AlertDialog(
               
onDismissRequest = {showAlertMessage.value = false},
               
title = {Text(text = "CONFIRM")},
               
text = {Text(
                       
text= "Hello User! To navigate to the next page, tap on NEXT button. Thank you.",
                       
modifier = Modifier.verticalScroll(rememberScrollState())
                )}
,
               
confirmButton = {
                   
val context = LocalContext.current
                    Button
(
                       
onClick = {
                           
showAlertMessage.value= false
                            val
intent = Intent(context, SecondActivity::class.java)
                            context.startActivity(intent)
                        }
,
                       
colors = ButtonDefaults.buttonColors(Color.Black)
                    ) {
                       
Text(text = "NEXT", color = Color.White, fontWeight = FontWeight.Bold)
                    }
                }
           
)
        }

       
Button(
           
onClick = { showAlertMessage.value= true},
           
modifier = Modifier.padding(6.dp),
           
colors = ButtonDefaults.buttonColors(Color.Magenta)
        ) {
           
Text(text = "Click Here to Proceed", fontSize = 18.sp)
        }
    }
}

@Preview(name = "Light Mode", showBackground = true, showSystemUi = true)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, showSystemUi = true)
@Composable
fun DemoScreenPreview(){
   
DemoScreen()
}

 

SecondActivity.kt

package com.ajirelab.demoone

import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.ajirelab.demoone.ui.theme.DemoOneTheme
import org.intellij.lang.annotations.JdkConstants.HorizontalAlignment

class SecondActivity : ComponentActivity() {
   
override fun onCreate(savedInstanceState: Bundle?) {
       
super.onCreate(savedInstanceState)
       
setContent {
           
DemoOneTheme {
               
// A surface container using the 'background' color from the theme
               
Surface(
                   
modifier = Modifier.fillMaxSize(),
                   
color = MaterialTheme.colorScheme.background
               
) {
                   
SecondScreen()
                }
            }
        }
   
}
}

@Preview(name = "Light Mode", showBackground = true, showSystemUi = true)
@Preview(
    name =
"Dark Mode",
   
showBackground = true,
   
uiMode = Configuration.UI_MODE_NIGHT_YES,
   
showSystemUi = true
)
@Composable
fun SecondScreenPreview() {
   
DemoOneTheme {
       
SecondScreen()
    }
}


@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SecondScreen() {

   
val navController = rememberNavController()
   
val bottomNavItems = listOf(NavigationItem.Home, NavigationItem.Info, NavigationItem.Settings)

   
Scaffold(
       
topBar = {
           
TopAppBar(
               
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Magenta),
               
title = {
                   
Text(text = "Demo One App", color = Color.White, fontWeight = FontWeight.Bold)
                }
,
               
navigationIcon = {
                   
IconButton(onClick = { }) {
                       
Icon(
                            Icons.Filled.
ArrowBack,
                           
contentDescription = "Back",
                           
tint = Color.White
                       
)
                    }
                }
,

               
actions = {
                   
IconButton(onClick = { }) {
                       
Icon(Icons.Filled.Home, contentDescription = "Home", tint = Color.White)
                    }

                   
//Codes related to DropDownMenu starts here
                   
val showMenu =
                       
remember { mutableStateOf(false) } //added for dropdown app bar menu
                   
IconButton(onClick = { showMenu.value = true }) {
                       
Icon(Icons.Default.MoreVert, contentDescription = null, tint = Color.White)
                    }
                   
DropdownMenu(
                       
expanded = showMenu.value,
                       
onDismissRequest = { showMenu.value = false }) {
                       
DropdownMenuItem(
                           
text = { Text(text = "About") },
                           
onClick = { showMenu.value = false })
                    }
                   
//Codes related to DropDownMenu ends here
               
}
           
) //TopAppBar ends here
       
}, //topBar ends here

       
bottomBar = {
           
NavigationBar(containerColor = Color.Black) {
               
val navBackStackEntry by navController.currentBackStackEntryAsState()
               
val currentRoute = navBackStackEntry?.destination?.route

               
bottomNavItems.forEach {
                   
NavigationBarItem(
                       
selected = currentRoute == it.route,
                       
label = {
                           
Text(
                               
text = it.label,
                               
fontSize = 16.sp,
                               
color = if (currentRoute == it.route) Color.LightGray else Color.Magenta
                           
)
                        }
,
                       
icon = {
                           
Icon(
                               
imageVector = it.icons,
                               
contentDescription = null,
                                
tint = if (currentRoute == it.route) Color.DarkGray else Color.Magenta
                           
)
                        }
,
                       
onClick = {
                           
if (currentRoute != it.route) {
                                navController.
graph?.startDestinationRoute?.let {
                                   
navController.popBackStack(
                                        it
,
                                        true
                                    
)
                                }
                               
navController.navigate(it.
route){
                                   
launchSingleTop = true
                               
}
                           
} //end of if statement
                       
} //end of onClick
                   
) //Closing parenthesis for NavigationBarItem
               
} //end of bottomNavItems.forEach
           
} // end of NavigationBar
       
} //end of bottomBar

   
) {//start of Scaffold body
       
NavigationController(navController = navController)
    }
}


@Composable
fun MainTextStyles(title: String) {
   
//The Spacer below is necessary so as to ensure that nothing is
    //hidden behind the TopAppBar.
   
Spacer(modifier = Modifier.height(60.dp))
   
Text(
       
text = title,
       
fontWeight = FontWeight.Bold,
       
textAlign = TextAlign.Center,
       
fontSize = 24.sp
   
)
}

@Composable
fun HomeScreen() {
   
Column(
       
modifier = Modifier
            .
fillMaxSize()
            .
padding(8.dp),
       
verticalArrangement = Arrangement.Center,
       
horizontalAlignment = Alignment.CenterHorizontally
   
) {
       
MainTextStyles(title = "This is the Home Screen")
    }
}

@Composable
fun InfoScreen() {
   
Column(
       
modifier = Modifier
            .
fillMaxSize()
            .
padding(8.dp),
       
verticalArrangement = Arrangement.Center,
       
horizontalAlignment = Alignment.CenterHorizontally
   
) {
       
MainTextStyles(title = "Here, you will gain access to more information about this app.")
    }
}

@Composable
fun SettingsScreen() {
   
Column(
       
modifier = Modifier
            .
fillMaxSize()
            .
padding(8.dp),
       
verticalArrangement = Arrangement.Center,
       
horizontalAlignment = Alignment.CenterHorizontally
   
) {
       
MainTextStyles(title = "Welcome to Settings Screen")
    }
}

@Composable
fun NavigationController(navController: NavHostController) {
   
NavHost(navController = navController, startDestination = NavigationItem.Home.route) {
       
composable(NavigationItem.Home.route) {
           
HomeScreen()

           
//Navigates to MainActivity when the user presses back button
           
val context = LocalContext.current
           
BackHandler {
               
Toast.makeText(context, "Thank you.", Toast.LENGTH_SHORT).show()
               
val intent = Intent(context, MainActivity::class.java)
                context.startActivity(intent)
            }
        }
       
composable(NavigationItem.Info.route) {
           
InfoScreen()

           
// Navigates to the startDestination of SecondActivity (that is, HomeScreen)
            // when the user presses back button
           
val context = LocalContext.current
            BackHandler
{
               
val intent = Intent(context, SecondActivity::class.java)
                context.startActivity(intent)
            }
        }
       
composable(NavigationItem.Settings.route) {
           
SettingsScreen()

           
// Navigates to the startDestination of SecondActivity (that is, HomeScreen)
            // when the user presses back button
           
val context = LocalContext.current
            BackHandler
{
               
val intent = Intent(context, SecondActivity::class.java)
                context.startActivity(intent)
            }
        }
    }
}

 

NavigationItem.kt

package com.ajirelab.demoone

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings
import androidx.compose.ui.graphics.vector.ImageVector

sealed class NavigationItem(val route: String, val label: String, val icons: ImageVector) {
   
object Home : NavigationItem("home", "Home", Icons.Default.Home)
   
object Info : NavigationItem("info", "Info", Icons.Default.Info)
   
object Settings : NavigationItem("settings", "Settings", Icons.Default.Settings)
}


Comments

Popular Posts

Android Development: Using Alert Dialog with Button in Jetpack Compose

  Introduction: In Android application development, buttons could be programmed to perform some actions (such as navigating to another screen, showing toast message, closing the app, among others) when the user taps or clicks on them. In this article, we will learn how to make a button to perform the action of displaying an alert message to the user. This would be achieved using a composable function called AlertDialog. Note that we will be demonstrating this with the 'Click Here to Proceed' button located inside the ‘DemoScreen’ composable function in our 'Demo One' Android project from my previous article. Demonstration: First, we will open the 'Demo One' project. ‘Demo One’ project is now opened as shown in Figure 1. Figure 1 Next, above the button composable in our ‘DemoScreen’ composable function, we will define a mutable state variable, set its value to ‘false’, and store it with the name 'showAlertMessage' (as shown in Figure 2). Note th

Literature Review: An Important Exercise for Students and Researchers

  Introduction Review of literature is a very important exercise which researchers as well as students at both undergraduate and postgraduate levels cannot shy away from, as it identifies and summarizes research and theories that are relevant to their area of interest, and as well sets good foundation for research or theses writing. In this article, we will consider some aspects of literature review such as its meaning, its significance, what it entails, steps to take in writing a good literature review, and some of the things that we need to avoid while reviewing literature. Meaning of literature review Let us first break down the key concepts and see what the dictionary has for us as per the meaning of ‘Literature’ and ‘Review’. In Oxford Advanced Learner’s Dictionary (8 th Edition), literature means “pieces of writing or printed information on a particular subject”. Also, Longman Dictionary of Contemporary English defines literature as "all the books, articles, etc.