Compose Destinations: simpler and safer navigation in Compose with no compromises

Rafael Costa
ProAndroidDev
Published in
6 min readJan 9, 2022

--

Hi all! 👋
It feels like a long time since I wrote the first post about Compose Destinations library.
Since then, I have been hard at work squeezing in a huge percentage of my free time to make it to where I’d be truly proud of it. After all that work, I think I got there with version 1.1.3-beta.
I can say: I am truly proud of it!

In this post, I will go through the advantages of using it, and, if all goes well, show you that it is worth a try.

Is there even a problem with using Jetpack Compose Navigation?

Do not get me wrong — I don’t think Compose Navigation is a disaster, or even bad at all, or I wouldn’t be building something on top of it. But, I do think that in terms of usability it can be greatly improved. It contains a lot of boilerplate and redundancy and the navigation arguments are not sent/received in a type-safe manner.
Let’s see an example.

  1. You have your screen Composables

2. You define some sort of construct to define routes

3. You make the NavHost call

Alright, after all that, you have four screens and navigation with arguments between them.

I did a search in the above snippets for “query” and found that I have seven instances of that parameter name 😮. This is what I mean by redundancy.
Even the type of this parameter and its nullability shows up multiple times! And in the end, even after all that, you still don’t get the argument in a type-safe way.

Simple Compose Destinations usage

Compose Destinations takes advantage of annotation processing (using KSP) to improve the usability of Compose Navigation.
Let’s jump right into the same example as before, now with Compose Destinations:

Hopefully, you’ve noticed how many lines of code we saved but more importantly how much safer and easier to write our code became.
All we had to do besides creating the Composables that actually show the UI was annotate them. Then we replaced NavHost with DestinationsNavHost and the navigation lambdas with DestinationsNavigator.

DestinationsNavigator is a wrapper interface to NavController that if declared as a parameter, will be provided for free by the library. NavController can also be provided in the exact same way, but it ties your Composables to a specific implementation which will make it harder to test and preview.

Just by doing that, notice the improvements in navigating, you just need to call the generated Destination‘s invoke function which will contain all the navigation arguments you defined in that destination’s Composable function including their nullability and default values. This returns a Direction which represents a route that can be safely used to navigate to. That Direction can then be passed to DestinationsNavigator.navigate function.
If the Destination has no navigation arguments, then it will itself implement the Direction interface, so, in that case, you can just pass the Destination to DestinationsNavigator.navigate.
This is yet another type-safety benefit of Compose Destinations: the APIs are clear about what kind of “route” they need. No longer will you stare at the, for example, clearBackStack which receives a “route” and question yourself what kind of route that is (it could be the route used to register in the NavHost or a route with the resolved navigation arguments).

Compose Destinations — FAQ

For this simple case, we clearly saw the advantages of using Compose Destinations. But for people that are used to Compose Navigation, some questions will surely pop up:

1.What if I want nested navigation graphs?

You just need to use navGraph argument of `@Destination` annotation to make that destination a part of a graph nested inside the “root” one.
If you need more control, then there is an alternative way to do it. Check the wiki here.

2. What if I need to pass non-navigation arguments to my Composables?

Compose Destinations can provide the following with no additional setup:

  • NavController (or NavHostController)
  • DestinationsNavigator
  • NavBackStackEntry

If you need to pass something else, for example, let’s say, the ScaffoldState, then you can specify how to call those specific Composables similarly to how you would with Compose Navigation:

Notice that you only need to pass the generated Destination object and you get navigation args in a type-safe way through the DestinationScope.

Keep in mind that if all your screens need a specific type of argument like the ScaffoldState, then you might want to consider CompositionLocal. If you go that route, then you suddenly don’t need to manually call all your Composables in that case.

3. What if some of my navigation arguments are Parcelable?

Contrary to popular belief, Jetpack Compose Navigation does allow you to pass Parcelable arguments. You do have to put quite a few pieces together though. Here too, Compose Destinations makes it much easier: you can just do it! It will work just like any other navigation argument, with one exception: if you need a deep link to that specific screen, then you need to tell the library how you want that Parcelable type to be represented in the route. For more info, read the wikis in this section.

Compose Destinations also allows Serializable (similarly to Parcelable) and Enums. Enums will be represented in the route by their entry names.

Keep in mind though that you should not be passing big objects with this method just like you should not pass big objects between Fragments. Nothing has changed here. For big data classes, consider saving them in a persistence store (like a Database) and just passing their ids around.

4. What if I don’t need the navigation arguments inside the Composable?

If you’re using a ViewModel, you might not actually want the navigation arguments in the Composable itself. Isn’t it awkward to have to declare them there?
Fortunately, Compose Destinations has a solution for this too. You just need to have a separate data class referenced in the `@Destination` via the navArgsDelegate argument. That data class‘s constructor will act the exact same way as if you were declaring the arguments in the Composable function:

Then you can get the instance of ProfileScreenNavArgs in the ViewModel by calling ProfileScreenDestination.argsFrom(savedStateHandle).

But there’s more than meets the eye

Besides what we’ve seen so far, you get quite a few more goodies when deciding to use Compose Destinations.
To name a few:

  • Easy integration with Accompanist Navigation-Animation and Accompanist Navigation-Material. This makes it easy to animate between destinations and use bottom sheet-styled destinations. Read more in this wiki.
  • Truly safe string navigation arguments.
    There are some edge cases to consider when you concatenate your routes with string arguments in the middle:
    - Empty strings;
    - Strings with characters like “&”, “/”, “?” and “%”.
    These can result in a badly formatted URI and crashes at runtime (or in receiving different strings from the ones sent) if you’re not really careful. Using this library means the strings you send will be the strings received on the other side, no need to worry about routes and their formatting.

Wrapping up

Compose Destinations is a library that I can now say with confidence will serve well from simple to more complex applications. It is a no-compromises choice when comparing it with “vanilla” Compose Navigation and the reason is simple: Compose Destinations builds on top of it and so you can do anything you could with it (and more) but just in a simpler and safer way.
I am absolutely certain that if you try it you will like it, and if you find a use case that can be improved or fixed you can submit an issue and I will be on top of it like a mad man 😄.
Who knows, maybe I can generate enough interest that someone will feel like contributing and helping me in this goal of making it easier for all developers navigating in Compose.
Either way, I will keep working on the library by providing updates, fixing issues, and adding features that make sense for its scope.

Let me know what you think about the library, if you’ve tried it, or if you still see any reason to use Jetpack Compose Navigation over it.
For anything else, hit me up on Twitter DM! I’m always up for a good conversation about Android development.
https://twitter.com/raamcosta

Here is the link to the GitHub project: https://github.com/raamcosta/compose-destinations

--

--

Android developer with a focus in writing clean and testable code, he loves to create tools for other developers even more than making Android apps.