Creating a BottomNavigation Multi-Stack using child Fragments with Simple-Stack
It’s been a recurring question, regardless of navigation framework of choice: how is it possible to create a screen with a bottom navigation view, that would host a navigation history stack per each tab?
Each framework has its own unique ways. For example, Jetpack Navigation provides NavigationExtensions as a sample, there’s FragNav, but how would you do it with Simple-Stack?
How bottom navigation with child fragments normally works
If you intend to create a BottomNavigationView that is hosted by a Fragment (and that Fragment hosts each tab as a child fragment), there’s a bit of work needed to be done. To keep the fragments alive while switching between them, they must all be added. However, to make sure that the fragments are in the correct lifecycle (stopped while not showing), it makes sense to detach them (and attach the one that is meant to be showing).
This example is also available here.
Please note that there is nothing specific to Simple-Stack in this snippet, this is how a fragment can manage its child fragments and keep only one of them attached, while the rest are detached.
In fact, the NavigationAdvancedSample works the same way.
We will use this knowledge later in the article.
What Simple-Stack normally does
When you use Simple-Stack, you typically install a Navigator
using Navigator.install()
. This allows a single global Backstack
instance to be accessible through the Activity’s context (and thus making it available to all views within the Activity), and provides automatic lifecycle management to ensure that navigation is handled correctly.
This is important to know, because if we want to create a Backstack
for each Fragment within our BottomNavigationView, we must manage the lifecycle (and keep the Backstack
instance alive across configuration changes) ourselves.
Thankfully, this is actually quite simple to do! After all, fragments give us all the callbacks we need for the lifecycle management, while ScopedServices can help us keep stuff alive across config changes.
Keeping the Backstack alive for each Fragment
To keep the backstack alive, I’ll introduce something called a FragmentBackstackHost
. It’s really just responsible for retaining the fragment across configuration changes (and also to intercept back presses for the current selected tab).
Managing the lifecycle of nested Backstacks
Now we need to manage the lifecycle of this Backstack
instance from a Fragment:
Registering the stack hosts as a scoped service
Now in order for the backstack fragment to see the stack hosts, we must provide them as a scoped service, so we will register it on our root screen:
Setting up the host fragments (and their child fragments)
Remember the snippet at the beginning, which allows us to select a fragment with a bottom navigation view, and show (attach) 1 fragment, while hiding (detaching) the rest?
That’s exactly what we need to do, just for these fragments that host a backstack each.
Navigating within a child fragment in a child stack
To navigate within a given child stack, all we need to do is use the child stack that was added with the help of the FragmentStackHost
.
This might seem a bit tricky, but after all, we are getting a service registered to the top-most parent fragment’s scope, and can identify the stacks with their provided names.
And with that, we’re actually done!
Conclusion
With that, we have created a Fragment, that hosts 3 child Fragments, and each of these 3 child Fragments hosts a Backstack of their own, that survives configuration changes and process death.
Was it tricky? Well, it requires knowledge of the Fragment lifecycle to set up, and knowing about FragmentTransaction.add, FragmentTransaction.attach, FragmentTransaction.detach
. However, this is also the same mechanism we’ve been using for years in ViewPager’s FragmentPagerAdapter
, so surely, it’s not too alien? 😅
Otherwise, for a problem that’s been unresolved for years, it didn’t seem that complicated to implement— with the added ability from Simple-Stack — that allowed Fragments to be capable of easily keeping things alive across configuration changes, and also be able to intercept back with an intuitive API.
The full sample code is available here, and the discussion thread is available here on /r/android_devs.