Compose gives Android developers a new way to implement UI. If you still hesitate to use it, you should read this article and be convinced. But Compose is not always so easy to use, let’s see it!
You want to use a bottom sheet with compose and be able to navigate from a bottom sheet to another with the Android back button managed like your sheet is a screen? It’s possible, there are several ways to do it.
First, let’s talk about the bottom sheet itself without combining it with navigation. With compose, there are 3 manners to implement it:
ModalBottomSheetLayout
BottomSheetScaffold
DialogFragment
Now, let’s combine a bottom sheet with the navigation. Thanks to Google Accompanist, we have a simple solution. Their solution is not perfect, but we found some tricky hacks to do exactly what we want.
But what do we exactly want? A picture is worth a thousand words:
This scheme represents what we want the most. Blue represents what doesn’t currently exist in the Google Accompanist library. Also, it’s not possible to skip the half extended position of the bottom sheet using the accompanist default implementation. As a bonus, it should be good to navigate to a second screen or to go to the bottom sheet “X B” from “Y A” (spoiler: it’s included in the library).
Use the compose library, for example use 1.2.0 version which is compatible with Kotlin 1.7.0
This library works with two key points.
ModalBottomSheetLayout
and wraps it with a BottomSheetNavigator
. This navigator is a normal navigator from androidx.navigation
that can manage the bottomSheetState (to show and hide bottom sheet) and navigate between the multiple bottom sheets.NavGraphBuilder
to specify a second kind of destination: bottomSheet
. That way, in the NavHost
where you specify the compose destination, the library has an indication to tell if you want to open a bottom sheet or just navigate to another full screen composable.Another advantage of using this library is that it already fixes some bugs linked to the bottom sheet itself. For example, the fact that a bottom sheet with 0dp height will crash. So your composable now looks like this:
Then you just need to navigate with the navController
to open bottom sheets and navigate between them.
A ModalBottomSheetLayout can be in 3 states : hidden, expanded and halfExpanded
. However, it’s very common to skip halfExpanded
state, and compose-material enables us to do so. Accompanist doesn’t let us do, so we have to create a function for it:
Passing a sheetState as a parameter enables us to skip the half expanded state if we want. It enables other possibilities we won’t discuss right now.
By default, when you navigate from a bottom sheet to another, you can use the Android back button to nav back (see Troubleshooting section if it’s not working). But if you close the sheet when you click on the backdrop or when you call hide()
on the state, you don’t want the previous bottom sheet from the back stack to show up.
Partial solution: navigate back to Home instead of calling hide()
You can do it easily on button click, but how to do it if the user swipes down the bottom sheet or clicks on backdrop? In these two cases, the onSheetDismiss
function from accompanist SheetContentHost is called. Unfortunately, we can’t change what it does without forking the library. So let’s fork Google Accompanist !First, We tried to pop to the first back stack entry: but by monkey testing we get the error below. Actually, it happens when I open a bottom sheet while it’s navigating between 2 of them. Indeed, during navigation, we quickly see the Home screen and click on it to open a new sheet. If you are in this case, back stack entries are bugged.
As a workaround, I define an optional function that replaces the accompanist pop function when the sheet is dismissed in the BottomSheetNavigator class. I just added the if part, the else is from the original library.
Now I just have to plug it to the navigation controller and pop to Home, same as the partial solution we saw earlier. To do so, I have to initialize the navigation controller before the BottomSheetNavigator
. Instead of adding the navigator at the initialization of the navigation controller, you add it when the navigator is created. Like this example:
Maybe you can find a solution that doesn’t need to fork accompanist, I think in the changeDestinationListener
of the navController
. Let me know if you can think of another solution!
To make easier the merge process with our fork when accompanist library is updated, I just made minimal changes to the original files:
BottomSheetNavigator
open to be extendable and make my own in my projectsheetContent
attribute open because it’s the only one I modifyIf the back navigation is not working on bottom sheets for your app, maybe another navController
is receiving this back click, so pass it to the bottomSheetNavController
by overriding onBackPress
. It usually happens if your composable is in a fragment managed in a navigation graph.
Google made a great library that simplified our work. But their Accompanist library is still experimental, so we probably will have some breaking changes in the future. Besides, compose bottom sheet itself is annotated as an experimental API. Even if Accompanist is maintained by famous people like Chris Banes or Nick Butcher, it’s still a library that you would move on to in the future, it’s just a temporary solution. If you browse the code, you’ll see some code smells.
If you really don’t want to use or fork the accompanist library, you can still put a NavHost
in the content of your bottom sheet using material BottomSheetScaffold or ModalBottomSheetLayout. You will just face some problems Accompanist resolves in their library.
Navigating between bottom sheets is a tricky process, I recommend avoiding it if you can. Think about the user experience, if you need to navigate from one sheet to another, maybe you are in a case that needs a full screen flow: is your background screen really necessary ? Is it just to avoid a data reloading when you come back to this screen ? You should read this article to choose the best UX for navigating.
I’m going to open some pull requests to the library, maybe someday this fork will be not needed.
As for now, I already opened an issue
Accompanist library:
https://google.github.io/accompanist/navigation-material/
My implementation and sample:
https://github.com/bamlab/android-navigable-bottom-sheet