Navigation with compose

Mercy Jemosop
4 min readMar 22, 2024

--

Handle navigation and redirection

Introduction

Hello, welcome to another session of learning kotlin. We are going to go over navigation in jetpack, we will handle navigation in the app and redirection from a web back to the application using deeplinks. Let’s goo!!!!

App Navigation is moving from one screen to another inorder to complete a task.

Deep link is a link that takes you to a specific destination within the application.

Setup android project

  1. Add a dependency to build.gradle.kts

Check the official documentation for updated nav_version

dependencies {
val nav_version = "2.7.7"

implementation("androidx.navigation:navigation-compose:$nav_version")
}

2. Create two composable screens to be used to test navigation

## Screen one

@Composable
fun ScreenOne(){
Button(
onClick={},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen One")
}

}

## Screen two
@Composable
fun ScreenTwo(){
Button(
onClick={},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen One")
}

}

we now have two composable screens to test our navigation between two screens.

3. setup our navigation

NavController

This is a one of the key concepts in navigation. It stores the navigation graph and makes available methods for your application to navigate between the locations shown in the graph.
Its the central navigation API and tracks which destination a user has visited and allows the user to move between destinations.

NavHost is used to create the navigation graph. Each NavHost you create has its own corresponding navController.

Navigation graph is a data structure that contains each destination within your app and connections between them.

val navController = rememberNavController()

NavHost(navController = navController, startDestination = "screenone") {
composable("screenone") { ScreenOne( ) }
composable("ScreenTwo") { ScreenTwo( ) }

}

we can refactor the above class by introducing a sealed class that contains the route and String resource id for the destinations.

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
object ScreenOne : Screen("screen_one", R.string.screen_one)
object ScreenTwo : Screen("screen_two", R.string.screen_two)
}

@Composable
fun AppRoute(navController: NavHostController) {


NavHost(navController = navController, startDestination = Screen.ScreenOne.route) {
composable(Screen.ScreenOne.route) { ScreenOne(navController) }
composable(Screen.ScreenTwo.route) { ScreenTwo(navController) }
}
}

Inside res/values/String.xml folder

<resources>
<string name="app_name">NavigationExample</string>
<string name="screen_one">ScreenOne</string>
<string name="screen_two">ScreenTwo</string>
</resources>

NavHost composable passes a NavController and a route string that corresponds to the start destination

NavHost(navController = navController, startDestination = "screenone")
  • The lambda passed to the NavHost ultimately calls the NavController.createGraph() and returns a NavGraph
val navGraph by remember(navController) {
navController.createGraph(startDestination = "profile") {
composable(Screen.ScreenOne.route) { ScreenOne( ) }
composable(Screen.ScreenTwo.route) { ScreenTwo( ) }
}
}
NavHost(navController, navGraph)
  • NavGraphBuilder.composable() adds destination to the resulting NavGraph.
  • “screenone” and “screentwo” is the key that identify the two destination.

Route is a String that defines a path to your composable.

Update our code with the navigation above

@Composable
fun ScreenOne(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Screen One")
Spacer(modifier = Modifier.height(60.dp))
Button(
onClick = {
navController.navigate("screentwo")
},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen Two", fontSize = 10.sp)
}
}
}

@Composable
fun ScreenTwo(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Screen Two")
Spacer(modifier = Modifier.height(60.dp))
Button(
onClick = {
navController.navigate("screenone")
},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen One", fontSize = 10.sp)
}
}
}

@Composable
fun AppRoute(navController: NavHostController,) {

NavHost(navController = navController, startDestination = Screen.ScreenOne.route) {
composable(Screen.ScreenOne.route) { ScreenOne( ) }
composable(Screen.ScreenTwo.route) { ScreenTwo( ) }

}
}

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
AppRoute(navController)
}
}
}

Passing arguments to another screen

Compose supports passing arguments between composable destinations. To achieve this we need to add argument placeholders to our route which are passed as string by default.

The arguments parameter of a composable accepts a list of NamedNavArgument object, the NamedNavArgument can be created using navArgument() method and specify its exact type.

    composable(
"screenthree/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {backStackEntry ->
ScreenThreeArg(navController, backStackEntry.arguments?.getString("userId"))
}
}

Display argument(userId) Text(text = “Value passed $userId”)

@Composable
fun ScreenThreeArg(navController: NavController, userId: String?) {

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Screen Three")
Text(text = "Value passed $userId")
Spacer(modifier = Modifier.height(60.dp))
Button(
onClick = {
navController.navigate("screentwo")
},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen two", fontSize = 10.sp)
}
}

pass argument to destination, you append it to the route when you make the navigate call.

 navController.navigate("screenthree/123 ")

Optional arguments

Navigation compose supports optional navigation arguments. This arguments differ from the required arguments in two ways:

  • They must be included using the query parameter
##syntax
"?argName={argName}")
  • They must have a default value or have nullable = true

All optional arguments must be explicitly added to the composable() function as list:

composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}

Note:

  • If there is no argument passed to the destination, the default value is used instead(user1234).
  • handling arguments through routes ensures means composables remain completely independent of navigation and make them more testeable.

Deeplinks with jetpack compose

Compose supports implicit deep links that can be defined as part of the composable() function. deeplinks parameter accepts a list of NavDeepLink object which can be created using navDeepLink().

   deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })

This concept is similar passing argument above:

 val uri = "https://stackoverflow.com"    
composable(
"screendeeplink?id={id}",
deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
ScreendeepLink(navController, backStackEntry.arguments?.getString("id"))
}

Deeplinks allows you to associate a specific url, action or mime type with a composable.
To expose deeplinks to external apps, we must add the appropriate <intent-filter> elements to our AndroidManifest.xml

       <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https" android:host="stackoverflow.com" />
</intent-filter>

test it by printing the value in composable screen

@Composable
fun ScreendeepLink(navController: NavController, value: String?) {

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Screen Three")
Text(text = "Value passed using deeplink :: $value")
Spacer(modifier = Modifier.height(60.dp))
Button(
onClick = {
navController.navigate("screentwo")
},
modifier = Modifier.width(300.dp)
) {
Text(text = "Screen two", fontSize = 10.sp)
}
}

Yeeh!!! We have covered most part of navigation in jetpack compose.

  • use ViewModel
  • Refactor route

Github link

--

--

Mercy Jemosop

Software Developer. I am open to job referrals. connect with me on twitter @kipyegon_mercy