Search code examples
androidandroid-jetpack-composeandroid-jetpack-navigationandroid-bottomnav

Jetpack Compose + Navigation - Nested navigation with BottomNavBar


I'm trying to implement the following screen flow using Jetpack Compose + Jetpack Navigation:

Navigation concept

Actually, i am able to code two singles cases:

  • SplashScreen --> HomeScreen (with no BottomNavBar)
  • HomeScreen (with BottomNavBar) --> Tabs

I'm not able to code the whole problem. In fact, i have an issue with the management of the NavHost. In the first case (SplashScreen -> HomeScreen) i need to call the NavHost at a high scope:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        MyAppTheme {
            //init the Navigation Controller for screen navigation
            val navController = rememberNavController()

            //setup the Navigation Graph
            SetupNavGraph(navController)

while in the second case i need to call it in the innerPadding scope of the Scaffold composable:

fun MainScreen(navController: NavHostController) {

    Scaffold(
        bottomBar = {
            BottomNavBar(navController)
        }
    ) { //innerPadding scope
        //setup the Navigation Graph
        SetupNavGraph(navController)
    }
}

Please assume that SetupNavGraph() function works as intended (call NavHost to generate the navigation tree)

  • I tried to use two NavHost without success.
  • If i setup the NavHost in setContent() i'm able to load the splashscreen and move to an empty BottomNavBar screen. If i click on the BottomNavElements i'm able to navigate to the child tabs (in the example above "Favorite","Music","Places", "News") but the BottomNavBar disappears
  • I cannot setup NavHost in the innerPadding scope because this is loaded only after switching to the main screen (in the example above "Favorite Tab" + BottomBarNav)

The only workaround i found is generating the BottomNavBar composable in each of the BottomNav child tabs, but this generates a visible transition effect that i would like to avoid and, generally, doesn't seem a good practice.


Solution

  • Ok, i found a solution. This are the steps to achieve the desired result:

    1. Create two different NavGraph, one for Splash->MainScreen and the other for the BottomNavBar
    const val ROOT_ROUTE = "root"
    
    @Composable
    fun SetupRootNavGraph(navController: NavHostController) {
        NavHost(
            navController = navController,
            startDestination = Screen.FirstScreen.route,
            route = ROOT_ROUTE
        ) {
            composable(Screen.FirstScreen.route) { FirstScreen(navController)}
            composable(Screen.SecondScreen.route) { MainScreen(navController)}
        }
    }
    
    const val BOTTOM_BAR_ROUTE = "bottomBar"
    
    @Composable
    fun SetupNavGraphBottomBar(navController: NavHostController) {
        NavHost(
            navController = navController,
            startDestination = BottomBarScreen.FirstElement.route,
            route = BOTTOM_BAR_ROUTE
        ) {
            composable(BottomBarScreen.FirstElement.route) { FirstElementScreen() }
            composable(BottomBarScreen.SecondElement.route) { SecondElementScreen() }
            composable(BottomBarScreen.ThirdElement.route) { ThirdElementScreen() }
        }
    }
    
    1. Init the NavController and the RootNavGraph after setContent() in your MainActivity. This will be in charge of the SplashScreen -> MainScreen navigation tree.
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyAppTheme {
    
                    //init the Navigation Controller for screen navigation
                    val navController = rememberNavController()
    
                    //setup the Root Navigation Graph
                    SetupRootNavGraph(navController)
                }
            }
        }
    }
    
    1. Re-init the NavController in the Screen where you have your BottomNavBar ("MainScreen" in the example) and the assign to it the BottomNavGraph in the innerPadding scope.
    @Composable
    fun MainScreen(navController: NavHostController) {
    
        //Re-initialize the NavController to set a new NavGraph
        val navControllerBottomBar = rememberNavController()
    
        Scaffold(
            bottomBar = {
                BottomNavBar(navControllerBottomBar)
            }
        ) {
            //setup the Navigation Graph
            SetupNavGraphBottomBar(navControllerBottomBar, user)
        }
    }
    

    And this will work like charm! Of course you will need to structure your BottomNavBar in order to manage the navigation as documented on Official docs