DEV Community

Cover image for Compose Destinations - Navigation Library
Vincent Tsen
Vincent Tsen

Posted on • Originally published at vtsen.hashnode.dev

Compose Destinations - Navigation Library

How to convert your Jetpack Compose navigation app to use Compose Destinations Library to get rid of boilerplate code?

This article was originally published at vtsen.hashnode.dev on April 30, 2022.

In my previous post, I build a very simple Jetpack Compose Navigation and use the NavRoute sealed class to avoid hard coding strings in multiple places.

However, a better solution may be just using this awesome Compose Destinations library! Let's see how we can convert this app to use this library.

Setup build.gradle (module level)

1. Add KSP Plugin

Add com.google.devtools.ksp plugin

plugins {
    ...
    id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
}
Enter fullscreen mode Exit fullscreen mode

2. Add Generated KSP Path

Add generated KSP path inside the android block

android {
    ...
    applicationVariants.all { variant ->
        kotlin.sourceSets {
            getByName(variant.name) {
                kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Add Compose Destination Dependencies

dependencies {
    ...
    implementation 'io.github.raamcosta.compose-destinations:core:1.5.1-beta'
    ksp 'io.github.raamcosta.compose-destinations:ksp:1.5.1-beta'   
} 
Enter fullscreen mode Exit fullscreen mode

Build Navigation Graph

Existing navigation graph related code (i.e. BuildNavGraph() and NavRoute) code can be removed completely and replaced with compose destinations annotations.

1. Annotate Screens with @Destination

Annotate all composable screens with @Destination

@Destination
@Composable
fun LoginScreen(
   ...
) {
...

@Destination
@Composable
fun HomeScreen(
   ...
) {
...
@Destination
@Composable
fun ProfileScreen(
   ...
) {
...
@Destination
@Composable
fun SearchScreen(
   ...
) {
...
Enter fullscreen mode Exit fullscreen mode

2. Annotate Start Screen with @RootNavGraph(start = true)

@RootNavGraph(start = true)
@Destination
@Composable
fun LoginScreen(
   ...
) 
...
Enter fullscreen mode Exit fullscreen mode

After you annotate the composable screen, make sure you Rebuild Project so all the necessary generated code will be generated.

3. Replace NavHostController with DestinationsNavigator

In the original login composable screen, it has this navigateToHome callback.

fun LoginScreen(
    navigateToHome: () -> Unit
) {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Now, it can be replaced with DestinationsNavigator parameter.

fun LoginScreen(
    navigator: DestinationsNavigator
) {
   ...
}
Enter fullscreen mode Exit fullscreen mode

To navigate, the original implementation use NavHostController

navController.navigate(NavRoute.Home.path)
Enter fullscreen mode Exit fullscreen mode

and now is replaced with DestinationsNavigator

navigator.navigate(HomeScreenDestination)
Enter fullscreen mode Exit fullscreen mode

HomeScreenDestination is the generated code.

Some other conversion examples below

// #1  - popBackStack() 
// convert NavHostController
navController.popBackStack()
// to DestinationsNavigator
navigator.popBackStack()

// #2 - navigate with arguments
// convert NavHostController
navController.navigate(NavRoute.Profile.withArgs(id.toString(), showDetails.toString()))
// to DestinationsNavigator
navigator.navigate(ProfileScreenDestination(7, true))

// #3  - popUpTo() 
// convert NavHostController
navController.navigate(NavRoute.Login.path) {
        popUpTo(NavRoute.Login.path) {inclusive = true}
}
// to DestinationsNavigator
navigator.navigate(LoginScreenDestination) {
   popUpTo(LoginScreenDestination.route) {inclusive = true}
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the DestinationsNavigator is basically a wrapper for NavHostController which makes it a lot easier.

4. Call DestinationsNavHost() in the main composable screen

Replace BuildNavGraph()

@Composable
private fun MainScreen() {
    SimpleNavComposeAppTheme {
        val navController = rememberNavController()
        BuildNavGraph(navController)
    }
}
Enter fullscreen mode Exit fullscreen mode

with DestinationsNavHost()

@Composable
private fun MainScreen() {
    SimpleNavComposeAppTheme {
        DestinationsNavHost(navGraph = NavGraphs.root)
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Use EmptyDestinationsNavigator in @Preview

Thanks to the author of this library, Rafael Costa told me that I can actually use EmptyDestinationsNavigator as null implementation and used it for @preview instead of passing innull.

Instead of passing in navigator = null, I can pass in navigator = EmptyDestinationsNavigator.

@Preview(showBackground = true)
@Composable
private fun DefaultPreview() {
    SimpleNavComposeAppTheme(useSystemUiController = false) {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colors.background
        ) {     
            HomeScreen(navigator = EmptyDestinationsNavigator)
       }
    }
}
Enter fullscreen mode Exit fullscreen mode

By doing that, I don't need to declare navigator: DestinationsNavigator? nullable variable parameter in the composable function.

Done!

Conclusion

This library is awesome! It gets rid of much boilerplate code. One thing I wish is I don't need to set up as in Step 1 - Add KSP Plugin and Step 2 - Add Generated KSP Path above, but maybe that is not technically feasible.

Source Code

See Also

Discussion (0)