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.
- Make the home a
route
instead of anavigation
and nest anotherNavHost
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.
- Create a single
Scaffold
for the entire app, then we would be able to display theBottomBar
if we're either one offavorites
,search
orprofile
.
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 ViewModel
s 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).
- And not really an option - Not using
compose-navigation
for thehome
route and just displaying the content based on awhen
statement (basically the same as no.1 but removing theNavHost
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.