Navigating in a Fullscreen Bottom Sheet on Android

David Marinangeli
flowe.ita
Published in
5 min readMar 12, 2022

--

Bottom Sheets are everywhere: they provide a fast, modern and cool way to interact with a task related to the parent view without taking the user away from the current context. Our colleagues on iOS use them for everything since Apple introduced the APIs and made them the default on its stock apps. But what about Android?

As you might know, the standard way to inflate a sheet is with BottomSheetDialogFragment that, you got it, extends DialogFragment, making it the worst candidate for anything related to navigation or a complex interaction. Here at flowe we love challenges and experimenting new ways to solve a problem: this was an interesting one!

Let’s proceed step by step:

  1. Make a Fullscreen Bottom Sheet Fragment
  2. Navigate inside it with Jetpack Navigation
  3. Ending with Style

0. The result

Before we start, let’s ask ourselves the most important question: what do we want to achieve? The product team asked us to provide a fast and reusable way to advice our users to upgrade their bank account to a new tier. This should be done in every feature that has “Pro” version: the goal is also to start and complete the flow within the same area that the user is exploring. This is what we’ve done!

1. How to make a Fullscreen Bottom Sheet Fragment

First of all, let’s stretch this component a little bit and focus on what we’re trying to achieve: we want to extend the BottomSheetDialogFragmentclass and make a component that fills the smartphone height.

open class FullScreenBottomSheetFragment() : BottomSheetDialogFragment() { ... }

To set the match_parent status as default we can define a new style, called, for example, CustomBottomSheetDialogTheme , that has simply two properties, the background ( not mandatory but it’s for the 3rd step of this article ) and the height.

<style name="CustomBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
<item name="bottomSheetStyle">@style/CustomBottomSheetStyle</item>
</style>

<style name="CustomBottomSheetStyle" parent="Widget.Design.BottomSheet.Modal">
<item name="android:background">@android:color/transparent</item>
<item name="android:layout_height">match_parent</item>
</style>

After that, we can call the onCreateView function, set the style we just created , the layout behaviour as always expanded and telling it to skip the status collapsed.

setStyle(DialogFragment.STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme)dialog?.setOnShowListener { dialog ->
val layout: FrameLayout? = (dialog as BottomSheetDialog).
findViewById(com.google.android.material.R.id.design_bottom_sheet)

layout?.let {
val behavior = BottomSheetBehavior.from(it)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
// TODO: NAVHOST INSTANCE
return view
}

Easy as that! You can paste this code on any BottomSheetFragment you already use and this will fill the whole screen.

<androidx.constraintlayout.widget.ConstraintLayout 
android:layout_width="match_parent"
android:layout_height="match_parent">

<FrameLayout
android:id="@+id/bottomSheetNavHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_bottom_sheet" />

</androidx.constraintlayout.widget.ConstraintLayout>

Like any other sheet of this kind, also this needs its layout, that will be composed of 1. Root layout 2. FrameLayout to use as a container of the fragments we will inflate in it. Our mission today, anyway, doesn’t stop here: we also have to navigate among various screens inside it!

2. Navigate inside a BottomSheetFragment with Jetpack Navigation

Ok, we have the custom sheet that we will use as a container: we just need to pass the navigation graph containing the whole flow we want to include, create the NavHostFragment, and set the graph. Let’s go!

First of all, we have to create a new navigation graph: let’s keep it simple and add only two fragments.

nav_bottom_sheet_example.xml<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottom_sheet_navigation"
app:startDestination="@id/fragmentOne">

<fragment
android:id="@+id/fragmentOne"
tools:layout="fragment"
android:name="com.example.FragmentOne">
<action
android:id="@+id/action_fragment_one_to_fragment_two"
app:destination="@id/fragmentTwo" />
</fragment>

<fragment
android:id="@+id/fragmentTwo"
android:name="com.example.FragmentTwo"/>

</navigation>

After this, we just need to add the graph name as a class parameter ( in this caseR.navigation.nav_bottom_sheet_example ) and show the BottomSheet as any other dialog you are used to.

Inside our FullscreenBottomSheetFragment we need to create an instance of NavHostFragment to inflate the fragment and set the graph we have created above. Let’s add it inside the onCreateView :

val navHost = NavHostFragment()
childFragmentManager.beginTransaction().replace(R.id.bottomSheetNavHost, navHost)
.commitNow()
navHost.navController.setGraph(navigationGraphId, bundle)

Thanks to this, we can re-use entire fragments and paste them into the navigation graph that has to be included in the sheet declaration. Last step: let’s show it!

FullScreenBottomSheetFragment(R.navigation.nav_bottom_sheet_example).show(childFragmentManager, null)

3. Ending with Style

The last part is about the style of our FullScreenBottomSheet: first of all, every fragment that you use inside it has to have match_parent height on the root layout or it won’t fill the whole screen.

If you wish to replicate iOS style ( with the stack effect ), you can add a custom background and some marginTop to the FrameLayout that contains every fragment.

Android
iOS

We combined the flexibility of a Fragment and the ease of use of a Bottom Sheet: we can still swipe a Viewpager, Recyclerview, Scrollview and everything that could clash with the Sheet basic interactions ( mainly the swipe down animation to close it ). What do you think about it? Let me know in the comments section!

If you like what we do and how we approach new problems and challenges, check out flowe’s website and our open positions!

Thanks Marco Acchini for the technical supervision and the help!

FLOWE — Pay better, be better

--

--

David Marinangeli
flowe.ita

Android Developer @ Flowe - Crazy for tech, basketball, cars and my 🐕