Jetpack Compose: Navigation

Android Nov 29, 2020

I consider Jetpack Navigation to be one of the most useful components of the Jetpack project. Sure, it's not perfect and some cases falls short, but in general it has solved a big hustle in the Android development world. For most apps, it's more than enough.

Naturally, I was quite curious on how navigation works in this new Compose world (in which we will live soon). It turns out, it's quite different but it makes sense since there is no XML in the new world (you used to define the navigation destinations and graph using XML). But still, gets the job done efficiently and quickly, more than enough for most apps.

Navigation in the Compose world still uses Jetpack Navigations classes but is extended to support @Composable functions. It works by swapping Composable views and keeping track of the stack.

Set up

Add these to your build.gradle. Check for the latest version first.

dependencies {
    [...]
    
    def nav_compose_version = "1.0.0-alpha02"
    implementation "androidx.navigation:navigation-compose:$nav_compose_version"
}

NavHost is the "drawing" area that screens/destinations will be drawn in. If you navigate to a new screen, the old one will be hidden and the new one will be shown.

Each screen/destination have a route. This concept is similar to some web frameworks. A route is a string that acts as a unique path to a screen (in this case a screen is a @Composable).

val navController = rememberNavController() // 1.
NavHost(navController, startDestination = "demo") { // 2.
    composable("demo") { ScreenDemo(navController) }
    composable("details") { ScreenDetails() }
}
  1. Use rememberNavController() to create a navigation controller that will be linked to your NavHost. Create this somewhere where you can pass it down to any screen that might need to do some navigation action.
  2. Create the NavHost and declare multiple destinations using the composable() method. This takes the path and the actual @Composable that will be called when navigating to this destination.

To navigate to a destination, use the NavHostController and the route path you declared in NavHost previously.

@Composable
fun ScreenDemo(navController: NavHostController) {
    Demo() { navController.navigate("details") }
}

@Composable
fun Demo(onClick: () -> Unit = {}) {
    Column {
        BasicText("Demo")
        Button(
            onClick = onClick,
        ) {
            BasicText(text = "Go to Details")
        }
    }
}

Note that navigate("string") is not a NavHostController method, but an extension method. You would need to import androidx.navigation.compose.navigate if the IDE fails to find the method.

Arguments

But what about arguments you might say? It's quite common for additional arguments to be passed around while navigating. Don't be intimidate by the example, bear with me.

NavHost(navController, startDestination = "demo") {
    composable("demo") { ScreenDemo(navController) }
    composable("details/{id}", // 1.
               arguments = 
                   listOf(navArgument("id") { type = NavType.IntType } // 2.
               { backStackEntry -> 
                   ScreenDetails(
                       navController,
                       backStackEntry.arguments?.getInt("id")!! // 3.
                   )
               }
}
  1. We added an argument to this path similarly to how you would do it in a web app. You can think of the route path as URL.
  2. Optionally, the type of this argument can be defined. This is almost always a good idea. By default, all arguments are parsed as Strings.
  3. We need to extract that argument value and pass it to your @Composable.

Optional arguments

Use the URL query parameters syntax to declare that an argument is optional. You must provide a defaultValue (or that is nullable).

NavHost(navController, startDestination = "demo") {
    composable("demo") { ScreenDemo(navController) }
    composable("details/{id}",
               arguments = 
                   listOf(navArgument("id") { 
                       nullable = true
                       type = NavType.IntType 
                   }
               { backStackEntry -> 
                   ScreenDetails(
                       navController,
                       backStackEntry.arguments?.getInt("id")
                   )
               }
}

Back

Navigate back by "popping" the current screen and going back to the previous one. This is the same method you would use in the View-based Jetpack Navigation.

@Composable
fun ScreenDetails(id: Int, navController: NavHostController) {
    Details(onClick = { navController.popBackStack() }, id = id)
}

@Composable
fun Details(onClick: () -> Unit = {}, id: Int) {
    Column {
        BasicText("Details $id")
        Button(
            onClick = onClick,
        ) {
            BasicText(text = "Back")
        }
    }
}

This was a super quick ramp up to how navigation is done in a Jetpack Compose app. Check out the documentation for further reading. And happy navigating around!


Check out more in this Jetpack Compose exploratory series:

Jetpack Compose: intro & basic layouts
Jetpack Compose is the latest and greatest way of designing UIs in the Android world. It offers a declarative way of building UIs using only Kotlin code.
Jetpack Compose: ViewModels
If you are have developed Android apps chances are you are familiar with ViewModel and unidirectional data flow. But how does this paradigm fits into this new Jetpack Compose world?

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.