Vincent Tsen
Android Kotlin Weekly

Android Kotlin Weekly

Compose Destinations - Navigation Library

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

Vincent Tsen's photo
Vincent Tsen
ยทApr 29, 2022ยท

3 min read

Compose Destinations - Navigation Library

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

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'
}

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")
            }
        }
    }
}

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'   
}

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(
   ...
) {
...

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

@RootNavGraph(start = true)
@Destination
@Composable
fun LoginScreen(
   ...
) 
...

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
) {
    ...
}

Now, it can be replaced with DestinationsNavigator parameter.

fun LoginScreen(
    navigator: DestinationsNavigator
) {
   ...
}

To navigate, the original implementation use NavHostController

navController.navigate(NavRoute.Home.path)

and now is replaced with DestinationsNavigator

navigator.navigate(HomeScreenDestination)

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}
}

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)
    }
}

with DestinationsNavHost()

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

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)
       }
    }
}

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

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

See recent sponsors |ย Learn more about Hashnode Sponsors
ย 
Share this