Simple Jetpack Compose Navigation Example
Tags: android, beginners, kotlin, jetpackcompose
Published: 2022-05-13 21:21:30

**Simple app to show different screens in Jetpack Compose and how to navigate between them.**

This is part of the Jetpack Compose navigation series:

*   **Part 1 - Simple Jetpack Compose Navigation Example**
    
*   [Part 2 - Compose Destinations - Navigation Library](https://vtsen.hashnode.dev/compose-destinations-navigation-library)
    
*   [Part 3 - How to Add Bottom Navigation in Jetpack Compose?](https://vtsen.hashnode.dev/how-to-add-bottom-navigation-in-jetpack-compose)
    
*   [Part 4 - How to Add Navigation Drawer in Jetpack Compose?](https://vtsen.hashnode.dev/how-to-add-navigation-drawer-in-jetpack-compose)

* [Part 5 - How to Add Deep Links in Jetpack Compose?](https://vtsen.hashnode.dev/how-to-add-deep-links-in-jetpack-compose)

I created this simple app to try out the [navigation component](https://developer.android.com/jetpack/compose/navigation) in Jetpack Compose. This is how the app looks like.

![Simple_Jetpack_Compose_Navigation_Example_01.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1643774613421/ZBMHbnJ3i.gif)

These are the steps to implement this simple app.

## 1. Add Navigation Compose Library

In `app\build.gradle`, add this dependency.
```
dependencies {
    implementation "androidx.navigation:navigation-compose:2.5.0-alpha01"
}
```

## 2. Create NavHostController
`NavHostController` is required to build the navigation graph in the next step which is used to navigate to different screens.

Create this `NavHostController` using `rememberNavController()` in your root composable function and pass that into the `NavGraph()` composable function.
```kotlin
@Composable
private fun MainScreen() {
    SimpleNavComposeAppTheme {
        val navController = rememberNavController()
        NavGraph(navController)
    }
}
```

## 3. Build Navigation Graph

The navigation graph looks like this:
- Login Screen -> Home Screen -> Profile Screen
- Login Screen -> Home Screen -> Search Screen
 
Login screen is the start destination. Home screen takes no navigation argument. Profile screen takes 2 navigation arguments and search screen takes 1 navigation argument.

To build the navigation graph, you use `NavHost()` composable function and `NavGraphBuilder.composable()` function to build each composable screen.


```kotlin
fun NavGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = "login"
    ) {
        composable(route = "login") {
            //call LoginScreen composable function here
        }

        composable(route = "home") {
            //call HomeScreen composable function here
        }
        ...
    }
}

```

To navigate to home screen:

```kotlin
navController.navigate("home")
```
To pop back current stack:
```kotlin
navController.popBackStack()
```
To pop up to login screen:
```kotlin
navController.popBackStack(NavRoute.Login.path, inclusive = false)
```

### Navigation With Arguments

`navGraphBuilder.composable` has 2 parameters - `route` and `arguments`.  To navigation with argument, we want to update both `route` and `arguments` parameters.

This is route format for profile screen. `id` is the first parameter. `showDetails` is the second parameter.
```kotlin
route = "profile/{id}/{showDetails}"
```
The `arguments` parameter looks like this:
```kotlin
arguments = listOf(
	navArgument("id") {
		type = NavType.IntType
	}
	,
	navArgument("showDetails") {
		type = NavType.BoolType
	}
)
```
You can specify the `NavType` of the parameter. You can also set the `defaultValue` to make the argument optional.

```kotlin
...
	navArgument("showDetails") {
		type = NavType.BoolType
		defaultValue = false
	}
...
```
> I personally will avoid using `defautValue` because it requires your route to follow certain format (i.e. `"profile/{id}/?showDetails={showDetails}"`). `?showDetails` is the optional arguement to allow you specify the `defaultValue`.

To retrieve the argument value, you use the `NavBackStackEntry.arguments`:
```kotlin
composable(
   ...
) { navBackStackEntry ->

	val args = navBackStackEntry.arguments

	// get id param value
	val id = args?.getInt("id")!!
	// get showDetails param value
	val showDetails = args?.getBoolean("showDetails")!!
    
    // call profile screen composable function here
	...
}
```
This is an example to navigate to profile screen.
```kotlin
val id = 7
val showDetails = true
navController.navigate("profile/$id/$showDetails")
```

### Any Route Format Is Fine!

Instead of using this (which I prefer because it is the simplest form):
```kotlin
route = "profile/{id}/{showDetails}"
```
You can use this (required if you want `showDetails` to be optional):
```kotlin
route = "profile/{id}/?showDetails={showDetails}"
```
Or this (required if you want both `id` and `showDetails` to be optional): :
```kotlin
route = "profile/?id={id}/?showDetails={showDetails}"
```
But please do NOT use this:
```kotlin
route = "profile/{id}{showDetails}"
```
> Please make sure you at least put a separator (any string) between the navigation parameters. This navigation parameters could be parsed wrongly especially they're same data type.

If you change the route format, you need to update the navigation call too. For example:
```kotlin
val id = 7 
val showDetails = true
navController.navigate("profile/$id/?showDetails=$showDetails")
```

## Too Much Boilerplate Code
You may have noticed, the hard-coded strings are everywhere.  One mistake can just crash the app. It is prone to errors.

So what I did is to create this `NavRoute` class that has all the hard-coded strings there. I also include a utility functions (i.e. `withArgs()` to build the navigation path and `withArgsFormat()` to build the `route` format string.

```kotlin
sealed class NavRoute(val path: String) {

    object Login: NavRoute("login")

    object Home: NavRoute("home")

    object Profile: NavRoute("profile") {
        val id = "id"
        val showDetails = "showDetails"
    }

    object Search: NavRoute("search") {
        val query = "query"
    }

    // build navigation path (for screen navigation)
    fun withArgs(vararg args: String): String {
        return buildString {
            append(path)
            args.forEach{ arg ->
                append("/$arg")
            }
        }
    }

    // build and setup route format (in navigation graph)
    fun withArgsFormat(vararg args: String) : String {
        return buildString {
            append(path)
            args.forEach{ arg ->
                append("/{$arg}")
            }
        }
    }
}
```
Some usage examples:
```kotlin
// navigate to home
navController.navigate(NavRoute.Home.path)

// navigate to search
navController.navigate(NavRoute.Search.withArgs(query))

// setup route for search screen with query param
route = NavRoute.Search.withArgsFormat(NavRoute.Search.query)
```
## Final Thoughts
I'm not sure my NavRoute approach above is the good one. I'm also not sure if it makes the code unreadable? Maybe a bit? However, it at least can get rid of many hard-coded strings and no more boilerplate code.

There is a **Compose Destinations** library which removes even more boilerplate code. I think it is better to understand the fundamental first before trying out this library. 

Here are the steps to convert this app to use this library:
- [Compose Destinations - Navigation Library](https://vtsen.hashnode.dev/compose-destinations-navigation-library)

## Source Code
GitHub Repository:  [Demo_SimpleNavigationCompose](https://github.com/vinchamp77/Demo_SimpleNavigationCompose)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/simple-jetpack-compose-navigation-example)*.