Search code examples
androidandroid-jetpack-composejetpack-compose-navigationadaptive-layout

How to implement nested navigation in a Two Pane layout in Jetpack Compose?


I'm developing an app with a Settings-like screen using Jetpack Compose, that adapts to different screen sizes. The app incorporates a NavGraph which has a top-level screen containing a LazyColumn. Clicking on an item in the LazyColumn takes the user to different destinations within the NavGraph. The destinations themselves can be graphs containing multiple screens, nested inside the top-level navigation component.

For small screen devices, I use a NavHost to navigate from the top-level screen to various nested screens. A typical nested navigation on mobile would look like this: Mobile Navigation

However, for large screen devices, I would like to use a Two Pane layout, with the LazyColumn positioned in the left pane and the selected destination in the right pane. The destinations may further lead to additional screens, which would open in the right pane of the Two Pane layout. Something like this: Tablet Navigation

Initially, I tried using separate NavHosts for each screen size, with one containing the top-level screen and the other without it. This would allow using one NavHost for small screen devices and the other NavHost to be embedded in the right pane on large screen devices. However, managing and synchronizing the open screens between the two hosts would be overly complex and impractical.


Solution

  • I was finally able to accomplish this with the help of this video: Navigation Compose on every screen.

    To achieve such a behavior, two NavHosts are required. One would be top-level and the other would be inside the NestedScreen. The top-level NavHost would look something like this:

    NavHost(navController = navController, startDestination = "topLevelRoute") {
        composable("topLevelRoute") { 
            TopLevelRoute(isExpandedWindowSize = isExpandedWindowSize, selectedItemId = selectedItemId)
        }
    
        composable("nestedScreen1") { NestedScreen1(...) }
        composable("nestedScreen2") { NestedScreen2(...) }
        ...
    }
    

    The TopLevelRoute composable with a TwoPane (available in Accompanist library) would look something like this:

    @Composable
    fun TopLevelRoute(
        isExpandedWindowSize: Boolean, 
        selectedItemId: String?,
    ) {
        if (isExpandedWindowSize) {
            val navController = rememberNavController()
            TwoPane(
                first = { TopLevelScreen(...) },
                second = {
                    NavHost(navController = navController, startDestination = "nestedScreen1") {
                        composable("nestedScreen1") { NestedScreen1(...) }
                        composable("nestedScreen2") { NestedScreen2(...) }
                        ...
                    }
                },
                ...
        } else {
            TopLevelScreen(...)
        }
    }
    

    Now, on a small screen device, when the user clicks on an item in the LazyColumn, we can navigate to the nested screen using the top-level navController while on a large screen device, we can navigate using the navController that is inside the TopLevelRoute.