Assume we are scrolling some shopping list and we open a specific item. While checking details we realize that we probably have something similar in our cart already, so we would like to change tab from listing to cart and check that out. For that, we would like bottom nav to save our progress, so when we come back to a listing tab, we are not starting from the beginning. In basic Android Compose Navigation we don't have that functionality. Let's create our own solution!
Section 1 - Basic bottom navigation
As the very base of our app we will have 2 bottom nav tabs:
Settings. Each of them will have 2 screens:
Inner. We need at least two of each to properly test our scenario.
Besides that, we need 2 screens alike but with just a different name
Home -> Settings.
Now we need
Destination tree for our routing, so we will know where we can navigate to. It's a nice practice to create separate class for it so we have everything structured.
Next, we have to create
NavigationBar, so we can inject it into our main application
Scaffold that will contain our app's
Last, but not least, we create NavGraphs for both home and settings screens. Here as well I will provide NavGraph for only Home destination to not create unnecessary snippets and keep article cleaner.
Now all we need to do is to invoke our
Navigation function in the
Preview of default bottom bar navigation behavior
Section 2 - Upgraded BottomBar
We have our base navigation, now we can start upgrading it.
This part of our bottom bar upgrade requires a little bit explanation. We are gonna have multiple
NavHostController instances. One for every bottom bar tab. For that, we need some way of differentiating them. The easiest way is just to use simple enum, but befor we create it, we should think of a way of using those controllers in a NavHost, right? Yes, and no. Because
1 NavHost = 1 NavHostController, so we cannot he multiple controllers in there.
If we cannot use more than one controller in one host, we have to create multiple hosts then. Learn how to do it!
That one is actually pretty simple. We can achieve that using
HorizontalPager. Pager will provide us lazy list behvaior, so we will be able to create nav host controller for each of our bottom destination. So, for some graphic visualization, let's change our
Navigation component a bit.
For now, it will not compile and IDE will show you few errors, but don't worry, we are gonna resolve them step by step. At first, we need previously mentioned
With that, we can not create our new
BottomBar class. It's main purpose will be to handle switching between bottom navigation and every other navigation functionality that will come to our mind. But what will such class need to work? For sure
PagerState to switch pages and probably
NavHostControllers, so we are gonna have specific instance at hock in one place, but with some way of differentiating it by our bottom destinations so they are not complete strangers to us.
Nice! We are doing great progress here! But it does not do anything at all...let's change that! What core purpose we would like that class to serve? What do you think about something like that:
Returning nav controller based on bottom destination:
Creating bottom nav bar component that will be displayed on the screen:
I think that's pretty decent functionalities. Now lets try and put them together.
If you want to now more about
@Stable annotation used for our new
BottomBar class check official android documentation.
In step 3 we introduced new component,
BottomNavigation. This is gonna be our new
BottomBar from previous section.
As you can see instead of
navController, we have
onClick as we are gonna change our bottom tab based on BottomDestinations index.
Now that we have all components that we were missing in our class, we can think of how should we create our bottom bar. Sure, we could simply create it just like that
val bottomBar = BottomBar(...), but would that really by sufficient? Yea, not so much. Besides, it will be much cleaner to have constructor like method. So, let's quote our bottom bar so we will have clean look at it.
Last but not least, summarize it. We need pager state, that will be easy, we just need to use
rememberPagerState(). What about our map? That's simple as well, we just gonna loop for our
BottomDestination values and for each use
There we go! As as addition I've added returning our
BottomBar instance as remember, so it will not change in any recomposition that is gonna occur.
rememberPagerState is experimental API and can change in the future.
At this point, we have everything created, we already used it in our
Navigation, so let's run app! But wait, there is still some error...ouh! We forgot about an important thing, obtaining route for each NavHost as
startDestination. This is gonna be similar to getting
BottomDestination by page, just add this code before last
Upgraded bottom bar navigation behavior with switching tabs
Section 3 - Additional functionalities
This section will contain 2 additional functionalities.
1. Going back to tab's initial view
Functionality that I personally like the most is going back to root on currently chosen tab click. Right now, clicking on selected tab does not change anything. But with just a little bit of effort, we can have it. Lets see:
First, add this method to the
BottomBar class, so we can use it anywhere we like. Then modify
BottomNavigation a bit.
The only thing that have changed was adding
onCurrentSelectionClick as an argument and it's usage in
Lastly, we have to add proper method while creating nav component and voila! We can happily use our new functionality!
Changing tab to the root one in upgraded bottom bar
2. Changing bottom bar visibility
It'd be pretty cool to be able to change bottom navigation's visibiity, right? For that, we will need support variable. You can put it right at the beginning of out
Then, we're gonna need some function that will handle change of visibility and we will have to make
BottomNavigation aware of new variable. So, as follows:
Of course we have to add
isActive = isBottomBarVisible.value to the creation of
BottomNavigation instance in
Last to add is invokation of this function. Let's say, we do not want to see our bottom navigation in settings inner screen. For that, we have to find our composable for it and simply invoke our new function.
Changing bottom bar's visibility in upgraded bottom bar
And thus we created saveable bottom bar navigation that will serve as well in our Compose world. What's even better about it (I didn't try it out, just assuming, but that's next thing to check!), is that this solution probably works with Compose Multiplatform as well! Probably with few changes for specific platform, but still, it should work.
Versions used in this article:
kotlin - 1.9.10
ComposeBom - 2023.10.01