6

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.

Frax
  • 387
  • 2
  • 12
  • Sorry I couldn't understand the question – RaBaKa 78 Feb 11 '22 at 00:38
  • Sorry and thx for editing. I added a more complete explanation and an example image of what i would like to achieve. Even if you can't answer, for the sake of being clear, could you tell me if what i ask is now understandable? Thx! – Frax Feb 11 '22 at 08:05
  • Yeah, It's now clear – RaBaKa 78 Feb 11 '22 at 12:43

1 Answers1

8

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

Dharman
  • 30,962
  • 25
  • 85
  • 135
Frax
  • 387
  • 2
  • 12
  • Question: Your MainScreen is part of which Graph? ROOT_ROUTE or BOTTOM_BAR_ROUTE ? – AkshayT Apr 13 '22 at 10:45
  • You found me at the end. :) It's root route. Btw, I suggest you to read another similar post. This solution is not the best if you have that a tab of the bottomnav need to navigate to a screen without bottomnav. – Frax Apr 13 '22 at 16:49
  • https://stackoverflow.com/questions/71395630/jetpack-compose-navigate-both-with-and-without-bottombar/71425065#71425065 this is the post I suggest you to read – Frax Apr 13 '22 at 16:55
  • 1
    But you lose the ability to navigate between the two graphs right? How do you go from the BottomNavGraph back to the MainNavGraph. EG if the main graph was a login flow, when your session expires one would want to navigate from the bottom graph back to the main graph – lostintranslation Jun 09 '22 at 15:36
  • You are absolutely right. Unfortunately I was not able to find a solution with nested navigation. I prefer the solution in the other post (the other one you commented) because is more consistent and organized, even if it's still more a workaround than a solution – Frax Jun 11 '22 at 07:24