Nested Navigation in Jetpack Compose

My explanation and breakdown on how to use Jetpack Compose Nested Navigation Graphs.

I'm just going to start this off being SUPER honest. I hate navigation with JC. Why? It's just trash. Android devs fought so hard for nav graph and then to have to revert to manually code the flow again feels like a big slap in the face.

Anyway it took me quite some time to understand the documentation around nested navigation so that I could have an auth flow and then my main app flow. I'm going to try to break it down here. (Resource at the end)

The Code

I don't want this to be super boring and technical so I added comments to the code and then included a high level breakdown.

Main Activity:

...
@Composable
fun Main(){
    val navController = rememberNavController()

    NavHost(navController, startDestination = StartupNavigationScreens.Login.route) {
        composable(StartupNavigationScreens.Login.route) { Login(navController) }
        composable(StartupNavigationScreens.Register.route) { Register(navController) }
        mainGraph(navController) //notice no composable but will go to the mainGraph
    }
}
...
composable in MainActivity.kt

Main Breakdown

In your main activity you should have your starter graph. In this case it's my "authentication graph". I recommend you start with just a movement between screen here before breaking out into the next step.

You might be wondering where this StartupNavigation class is. Well it's in the Navigation.kt code below. I did that to keep MainActivity clean. It's a view and as such I want to keep view code only in there.

I want you to notice that the mainGraph isn't wrapped in a composable like the other screens. Why? Well when I initially did it that why the navigation "worked" but there was a blank screen. I'll explain where that code went below.

Navigation:

// Authentication screens
sealed class StartupNavigationScreens(val route: String) {
    object Login : StartupNavigationScreens("login")
    object Register : StartupNavigationScreens("register")
    object Main : StartupNavigationScreens("main")
}

// Main app screens
sealed class MainNavigationScreens(val route: String) {
    object Home : MainNavigationScreens("home")
    object Secondary : MainNavigationScreens("second")
}

/*
Notice not a composable. Just an extended method for NavGraphBuilder.
Also notice that what would have been `composable(StartupNavigationScreens.Main.route)` is now `route = StartupNavigationScreens.Main.route`
/*
fun NavGraphBuilder.mainGraph(navController: NavController) {
    navigation(startDestination = MainNavigationScreens.Home.route, route = StartupNavigationScreens.Main.route) {
        composable(MainNavigationScreens.Home.route) { Home(navController) }
        composable(MainNavigationScreens.Secondary.route) { Secondary(navController) }
    }
}
Navigation.kt

So here you will notice a few things:

  • I have the sealed classes in this file
  • We use NavGraphBuilder.graphName
  • The route for the mainGraph is equal what would have been the composable route

To start with readability I think this is the perfect way to setup your navigation files. If you needed to nest another graph for say a profile workflow then I would highly recommend making a new file and doing the work there. The way navigation is written can make the readability become very confusing for other people to read so let's try to keep it as clean as possible here.

Now, back to the code breakdown...notice the route from StartupNavigationScreens being the way to connect to the main graph. Now you might be asking "why is this?". I truly don't know. I'm assuming it has something to do with the route naming. Every route = is a way to give a graph a unique name. In this case your saying this route name = main graph and at that point the graph details take over.

Lastly I'll mention NavGraph.graphName. Notice that this is not a composable function, BUT in MainActivity.kt the main navigation is inside a composable. That is because we are extending the functionality of the navHost. If you ctl + click on NavGraphBuilder you can see the break down yourself. The main thing I want you to remember is the name after the . is what you use to call the nested graph, and this is not a composable function.

Conclusion

TLDR: I want JC to use the previous navigation graph. If the purpose is getting up and running in a timely fashion then I think that step is imperative.

I hope I've brain dumped enough of my lessons so you can also understand nested graphs. The documentation was really confusing to me because I was at first using bottom navigation (where they recommend sealed classes for screens), but the main navigation section isn't written with that concept in mind.

I kinda want JC to succeed but I think issues like this where it takes longer to connected the dots to solve my problems will be the reason I go back to "traditional" Android apps. If Google doesn't want this I highly recommend that they pivot faster for clarity.

Resources

Check out my jetpack compose template repo here (this code came from LoginFlow): https://github.com/Keheira/Jetpack-Compose-Templates

P.S. When are we getting ide templates for JC Google??????