Navigation : Jetpack Compose

Richa Sharma
5 min readSep 29, 2024

--

Overview

https://unsplash.com/
  • In this article, we are going to take a look at how we can perform navigation through screens in Jetpack Compose.

Navigation refers to the interactions that let users navigate across, into, and back out from the different pieces of content within your app.

  • Before starting, there are a few key concepts related to navigation in Android that you should be aware of.
  1. Host : Basically known as NavHost, it is a composable that manages which screen is displayed based on a given route.
  2. Controller : Specifically a navigation controller, is a type of view controller that manages the navigation stack of other view controllers.
  3. Destination : It’s basically a route to define a starting destination to launch the respective screen.
  4. Graph : The Navigation graph is an XML file that contains navigation information.

Sample

  • To start working with Navigation in compose, we need to add this dependency in build.gradle
dependencies {

implementation("androidx.navigation:navigation-compose:2.8.1")
}
  • Let’s first create a new Navigation composable function that will define the NavHost. In this example, we will pass a value from one screen to another through Navigation.
  • In the Navigation function, we need a navController, which is an instance of NavController, and with it, we can navigate wherever we want. In Compose, we can define it using the method rememberNavController().
  • Now that we have a navController and each NavController must be associated with a single NavHost composable, we can define NavHost() by passing the navController value and StartDestination.
@Composable
fun AppNavigation() {
val navController = rememberNavController()

NavHost(navController = navController, startDestination = "") {

}
  • StartDestination : The difference we have in XML navigation with Compose is that we don’t need a nav graph in the NavHost. Instead, we can use StartDestination since we don’t require transitions between fragments; we can specify the routes, which are specific strings. The startDestination is the route that defines the screen to be displayed first.
  • Now we need to pass the screen in which we need to start so we can create a sealed class name as “Screens” and in which we can define the two Screens which will be shown in app i.e FirstScreen and SecondScreen and define a param as “route
sealed class Screens(val route: String) {
object FirstScreen: Screens("first_screen")
object SecondScreen: Screens("second_screen")
}

Sealed class — basically a class that restricts the class hierarchy to a specific set of subclasses known at compile time.

  • And we can craete 2 new composable functions FirstScreen and SecondScreen and now in NavHost we can pass startDestination as First Screen.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screens.FirstScreen.route) {
//
}
}
@Composable
fun FirstScreen(navController: NavController) { // }
@Composable
fun SecondScreen() { // }
  • We need to define a navController in FirstScreen because it contains a button through onclick we will navigate to another screen and to navigate we need navController.
  • Now we can design our first screen simple ui and add a Column in which we have a TextField and a Button.
@Composable
fun FirstScreen(navController: NavController) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 30.dp)
) {

TextField(value = "" , onValueChange = {
//TODO
},
modifier = Modifier.fillMaxWidth())
Spacer(modifier = Modifier.height(5.dp))
Button(onClick = { //TODO },
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "Save")
}
}
}
  • Now in TextField we need a value and onValueChange text now to do this this we can define a text to maintain state.
  • we can define a text and mutableStateOf() -> The MutableState class is a single value holder whose reads and writes are observed by Compose. Additionally, writes to it are transacted as part of the Snapshot system.
@Composable
fun FirstScreen(navController: NavController) {
var text by remember {
mutableStateOf("")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 30.dp)
) {

TextField(value = text, onValueChange = {
text = it
},
modifier = Modifier.fillMaxWidth())
Spacer(modifier = Modifier.height(5.dp))
Button(onClick = { navController.navigate(Screens.SecondScreen.route) },
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "Save")
}
}
}
  • Define a text inside our SecondScreen which will show the text that we have passed from FirstScreen. To get text value we can define a string param in SecondScreen
@Composable
fun SecondScreen(name: String?) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Text(text = "Hello $name")
}
}
  • In the FirstScreen button click we have passed “Button(onClick = { navController.navigate(Screens.SecondScreen.route) }” but this will not pass the text value from the first screen to another to do that we need to pass it as an argument.
  • So we can define this arguement in our NavHost composable{}.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screens.FirstScreen.route) {
composable(route = Screens.FirstScreen.route) {
FirstScreen(navController = navController)
}
composable(route = Screens.SecondScreen.route + "/{name}", arguments = listOf(
navArgument("name"){
type = NavType.StringType
nullable = true
}
)) { entry -> //navBackStackEntry
SecondScreen(name = entry.arguments?.getString("name"))
}
}
}
  • here we have 2 composable in the second composable we have to define list of arguments in which we have used “navAgrument()” in which we can define an argument type, default value and set nullable. and in this composable function we can define this is SecondScreen and to get the name argument here we can use navBackStackEntry.
  • we can create a help function to get the argument in Screen class
sealed class Screens(val route: String) {
object FirstScreen: Screens("first_screen")
object SecondScreen: Screens("second_screen")
fun withArgs(vararg args: String): String {
return buildString {
append(route)
args.forEach { ar ->
append("/$ar")

}
}
}
}
  • and now in FirstScreen onClick we can pass the text value in withargs() method.
@Composable
fun FirstScreen(navController: NavController) {
var text by remember {
mutableStateOf("")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 40.dp)
) {

TextField(value = text, onValueChange = {
text = it
},
modifier = Modifier.fillMaxWidth())
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { navController.navigate(Screens.SecondScreen.withArgs(text)) },
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "Save")
}
}
}

--

--

No responses yet