1

Let's say I have this navigation structure:

That will look something like this with compose-navigation:

NavHost(navController = navController, startDestination = "login") {
    composable("login") {
        Text("Login Screen!")
    }
    navigation(route = "home", startDestination = "favorites") {
        composable("favorites") {
            Text("Favorites Screen!")
        }
        composable("search") {
            Text("Search Screen!")
        }
        composable("profile") {
            Text("Profile Screen!")
        }
    }
}

My issue is that I want to use compose-navigation to navigate to an "inner" screen inside home while still maintaining a shared component between all of home's routes (e.g. favorites, search & profile), this shared component may be a BottomAppBar for example. Please notice that the login for example doesn't have a bottom bar.

I have found multiple solutions to this problem but each and every one of them comes with a caveat that makes it difficult to use.

  1. Make the home a route instead of a navigation and nest another NavHost inside of it. This allows to do the following:
NavHost(navController = navController, startDestination = "login") {
    composable("login") {
        Text("Login Screen!")
    }
    composable(route = "home") {
        val homeNavController = rememberNavController() //Second NavController!
        Column {
            NavHost(navController = homeNavController, startDestination = "favorites") {
                composable("favorites") {
                    Text("Favorites Screen!")
                }
                composable("search") {
                    Text("Search Screen!")
                }
                composable("profile") {
                    Text("Profile Screen!")
                }
            }
        }
        BottomAppBar {
            //Showing all items and onClick navigating
        }
    }
}

As you can see, because NavHost's builder lambda is a @Composable, we can just add a Column and show the BottomAppBar below the NavHost (which is basically the "inner" screen). From what I've seen, this solution is error-prone because you'll have to maintain two distinct NavControllers that can't know anything about each other, also, I think its considered an anti-pattern.

  1. Create a single Scaffold for the entire app, then we would be able to display the BottomBar if we're either one of favorites, search or profile.
val currentDestination = ... //String
Scaffold(
    bottomBar = {
        when (currentDestination) {
            "favorites", "search", "profile" -> BottomAppBar {
                //Showing all items and onClick navigating
            }
            else -> Unit
        }
    }
) {
    NavHost(
        navController = navController,
        startDestination = "login",
        modifier = Modifier.padding(it)
    ) {
        composable("login") {
            Text("Login Screen!")
        }
        navigation(route = "home", startDestination = "favorites") {
            composable("favorites") {
                Text("Favorites Screen!")
            }
            composable("search") {
                Text("Search Screen!")
            }
            composable("profile") {
                Text("Profile Screen!")
            }
        }
    }
}

Here, because the BottomBar is composed whenever the app's current route is changed, we do a when and decide which composable to show (for example if it's login we don't show a BottomBar. This solution is overall good but it makes the screen separated from the Top/Bottom bar, thus creating more complexity when we'll add ViewModels to the mix because we'll have to inject the same instance to both the screen and the Top/Bottom bar composables (which is not that horrible but it does get really messy if you do anything extra).

  1. And not really an option - Not using compose-navigation for the home route and just displaying the content based on a when statement (basically the same as no.1 but removing the NavHost and doing a "manual" check to decide which screen to draw). This just loses all of the navigation capabilities.

After a lot of thinking about this (relatively simple) problem, I'm surprised no one has yet to ask this, and also that Google doesn't have any documentation about a best-practice solution for this.

Would love to hear solutions regarding this. Thanks.

Ofek
  • 324
  • 3
  • 13

1 Answers1

0

Did you read that ? Should I use Scaffold in every screen ? what are best practices while using topBar, bottomBar, drawer, etc. in compose

As you said, every solution has drawbacks. But i'd prefer option 2 because i think you can easily make it less painful by using a view state for your bottom bar generation. This view state can be updated by each screen itself.

DamienL
  • 577
  • 1
  • 5
  • 14
  • Hey Damien, Unfortunately this was a long time ago so I had to choose one solution and solution number 2 was indeed the one I went with, plus I've found a few ways to make stuff easier to work with. Seems like there's no specific guideline for this yet but at least a bunch of us think similarly, thanks for you response though :) – Ofek Apr 22 '23 at 09:43