Jetpack compose navigation with custom  NavType

Jetpack compose navigation with custom NavType

Navigation refers to the interactions that allow users to navigate across, into, and back out from the different pieces of content within your app. The Navigation library also provides a specific artifact to enable consistent and idiomatic navigation within screens built with Jetpack Compose.

When using navigation with jetpack compose, we define a nav host using the NavHost composable which provides a lambda function in which we can add a destination to the NavGraphBuilder.

Suppose that we wish to navigate between ScreenA and ScreenB It'll look like

NavHost(navController = navController, startDestination = "ScreenA") {
    composable("screen-a") { ScreenA(/*...*/) }
    composable("screen-b") { ScreenB(/*...*/) }
}

By default, all arguments are parsed as strings

As the documentation said, navigation compose also supports passing arguments between composable destinations and by default, all arguments are parsed as strings.

NavHost(startDestination = "screen-a") {
    ...
    composable(
        "screen-b/{id}",
        arguments = listOf(navArgument("id") { type = NavType.StringType })
    ) {
           ScreenB(/*...*/)
      }
}

You should extract the NavArguments from the NavBackStackEntry that is available in the lambda of the composable() function.

composable("screen-b/{id}") { backStackEntry ->
    ScreenB(id = backStackEntry.arguments?.getString("id"))
}

To pass the argument to the destination, you need to add the value to the route in place of the placeholder in the call to navigate:

navController.navigate("screen-b/1")

Creating a custom data type

The above way works in most cases. You just have to pass the id and then fetch the additional information from the database. But sometimes you just want to pass your data to another screen without doing an additional request.

Jetpack navigation component allow us to create custom NavType.

NavType denotes the type that can be used in a NavArgument.

Suppose that we want to pass a custom Post data type to ScreenB

data class Post(val id: Int,val title: String,val content: String)

We can create a "Post NavType", Post must first be parcelable.

@Parcelize
data class Post(val id: Int,val title: String,val content: String) : Parcelable

Then we can create PostType class

class PostType : NavType<Post>(isNullableAllowed = false) {
    override fun get(bundle: Bundle, key: String): Post? {
        return bundle.getParcelable(key)
    }

    override fun parseValue(value: String): Post {
        return Gson().fromJson(value, Post::class.java)
    }

    override fun put(bundle: Bundle, key: String, value: Post) {
        bundle.putParcelable(key, value)
    }
}

The PostType extend from NavType which has the Post data class as type parameter, i also use Gson in order to deserialize the Post from argument which w'll be a json, but you can use the convertor that you prefer.

Retrieve data

we must update the NavGraph in order to be able to retrieve the post

NavHost(startDestination = "screen-a") {
    ...
    composable(
        "screen-b/{post}",
        arguments = listOf(navArgument("post") { type = PostType() })
    ) {
           val post = it.arguments?.getParcelable<Post>("post")
           ScreenB(post)
      }
}

Navigate

Now the question is about how to pass the post as parameter. First we must serialize and then encode it to uri.

/*...*/
onclick = {
    val post = Post(1, "My post", "this is my wonderfull post content")
    val json = Uri.encode(Gson().toJson(post))
    navController.navigate("screen-b/$json")
}
/*...*/

This works well, but we can create a function in which we can encapsulate the serialization line, in my case a overloaded the toString() method.

override fun toString(): String {
    return Uri.encode(Gson().toJson(this))
}

Now we can just use string interpolation

/*...*/
onclick = {
    val post = Post(1, "My post", "this is my wonderfull post content")
    navController.navigate("screen-b/$post")
}
/*...*/

Conclusion

Despite the fact that the Jetpack Compose team doesn’t recommend passing Parcelable in the navigation composable routes, you may sometimes still need to pass a custom data type as parameter and I hope this blogpost can help you...

References