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.
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.
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.
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 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.
Declaration of navController and bottomNavItems Variables
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.
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.
Addition of BackHandler to Each Screen
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
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).That’s
it.
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
Post a Comment