How to convert your Jetpack Compose navigation app to use Compose Destinations Library to get rid of boilerplate code?
This is part of the Jetpack Compose navigation series:
Part 2 - Compose Destinations - Navigation Library
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'
}
For
build.gradle.kts
example, you can refer to this commit here.
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 forNavHostController
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
- Conversion diff is here.
- GitHub Repository: Demo_SimpleNavigationCompose (compose_destinations branch)
See Also
Originally published at https://vtsen.hashnode.dev.
Top comments (3)
Great!
I tried to use the library, the only issue I have is, when I use it to navigate to next screen, scaffold gets hidden like top and bottom nav bars they will be gone!
Any idea?
I think you need to manually pass in the
NavHostController
to your custom Top/Bottom bar composable function. Then, use it to decide whether you want to show them or not based on the specify route destination. Something like this:You can create
NavHostController
like this and pass it in as a parameter into your top/bottom bar and also theDestinationsNavHost()
thanx man , this was awesome :)