Material3 BottomSheet Navigation

A new library to simplify Bottomsheet Navigation with Material3

Stefano Natali
4 min readJul 7, 2024

Many of us, myself included, encountered a specific hurdle during the Material3 migration: navigation with bottomsheets.

In the Material design era, achieving bottomsheet navigation was possible thanks to libraries like Accompanist Navigation Material and the official androidx.compose.material.navigation. These libraries seamlessly integrated with your project, allowing you to define bottomsheets as navigation routes alongside regular screens. This proved immensely valuable for apps with extensive use of bottomsheets for various functionalities.

However, the Materail3 migration threw a big impediment. These navigation libraries for bottomsheets turned out to be tightly coupled with the Material library. This presented a dilemma:

  1. Eliminate Bottomsheet Routes: Abandoning the concept of bottomsheets as routes. That meant resorting to standard ModalBottomSheet instances, sacrificing the integrated navigation experience.
  2. Double Material Dependencies: Introducing the Material library alongside Material3 created a dependency management headache. Carefully choosing components became crucial to avoid inconsistencies. This can be a constant source of frustration and potential bugs.

Faced with these suboptimal choices, I embarked on a personal quest: implementing bottomsheet navigation with Material3 from scratch. This test proved successful, and the resulting solution not only streamlined my app’s navigation but also offered a Material3-consistent experience. Inspired by this success, I decided to create a reusable library and share it as a GitHub package.

As a second step, I further enhanced the library by incorporating a new functionality recently introduced in androidx.navigation:navigation-compose 2.8.0. This allows defining bottomsheet routes using data objects/classes instead of plain text. This addition provides greater flexibility and data-rich routing capabilities.

Curious about using this library? I’ll guide you through its simple integration steps in the next section. For those who prefer a code-first approach, the complete implementation is available here.

Library Integration

Before we dive into using the library, let’s ensure it’s properly integrated into your Compose project.

If you’re already leveraging version catalogs in your project, here’s how to add the necessary dependencies in the libs.versions.toml for this library:

[versions]
...
navigationCompose = "2.8.0-beta04"
material3Navigation = "X.X.X"
material3 = "1.3.0-beta04"

[libraries]
...
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3"}
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
material3-navigation = { group = "io.github.stefanoq21", name = "material3-navigation", version.ref = "material3Navigation" }

Check the current version here. And finally in your build.gradle.kts

...
dependencies {
...
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.material3.navigation)

Great news! The library is now available on Maven Central. This means you don’t need to do nothing else! Once you’ve added the dependency, you’ll have everything you need to start using the library.

Usage

Now that the library is integrated, let’s dive into using it for smooth Material3 bottomsheet navigation. The cornerstone of this process is the BottomSheetNavigator class provided by the library. This navigator manages bottomsheet destinations within your navigation graph.

One crucial aspect of configuring the BottomSheetNavigator is specifying whether your bottomsheet routes should skip the partially expanded state. This state refers to the intermediate stage where the bottomsheet is partially visible on the screen.

 val bottomSheetNavigator =
rememberBottomSheetNavigator(skipPartiallyExpanded = true/false)
val navController = rememberNavController(bottomSheetNavigator)

With the BottomSheetNavigator defined, let’s explore how to display the bottomsheet content. This is where the ModalBottomSheetLayout component provided by the library comes into play.

The ModalBottomSheetLayout acts as a container for your bottomsheet content. It seamlessly integrates with your navigation graph by accepting the BottomSheetNavigator as a parameter during its creation. Here’s how to incorporate it:

ModalBottomSheetLayout(
modifier = Modifier
.fillMaxSize(),
bottomSheetNavigator = bottomSheetNavigator
) {
NavHost(
navController = navController,
startDestination = Screen.Home
) {

Now that the ModalBottomSheetLayout is set up, let's explore how to define routes for your bottomsheet destinations. The approach for defining these routes depends on the version of Compose Navigation you're using:

  • Using Strings (Compose Navigation versions < 2.8.0):

If you’re using an older version of Compose Navigation, you can define routes as simple strings. These strings represent unique identifiers for each bottomsheet destination.

bottomSheet("BottomSheetFullScreen") {
BSFullScreenLayout()
}
  • Using Data Classes (Compose Navigation versions >= 2.8.0):

For newer versions of Compose Navigation (more details here), you can leverage the power of data classes/objects for defining routes. This allows you to encapsulate additional information about your destinations within the route itself.

 @Serializable
data object BottomSheetFullScreen : Screen
bottomSheet<Screen.BottomSheetFullScreen> {
BSFullScreenLayout()
}

Ready to Navigate! With routes defined, you can now navigate to your bottomsheet destinations using the standard navigate function provided by Compose Navigation.

Button(onClick = { navController.navigate(Screen.BottomSheetFullScreen) }) {
Text(text = "BottomSheetFullScreen")
}

Customization

The library empowers you to create bottomsheets that perfectly align with your app’s design and functionality. It currently supports the same customization options of the standard material3.ModalBottomSheet. You can customize the appearance of all the bottomsheets used in your navigation graph by passing the parameters to the ModalBottomSheetLayout.

Here the definition of the component to give you an idea:

@Composable
fun ModalBottomSheetLayout(
bottomSheetNavigator: BottomSheetNavigator,
modifier: Modifier = Modifier,
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
shape: Shape = BottomSheetDefaults.ExpandedShape,
containerColor: Color = BottomSheetDefaults.ContainerColor,
contentColor: Color = contentColorFor(containerColor),
tonalElevation: Dp = BottomSheetDefaults.Elevation,
scrimColor: Color = BottomSheetDefaults.ScrimColor,
dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
content: @Composable () -> Unit,
)

Conclusions

With this guide, you’ve explored the capabilities of my new library for streamlined Material3 bottomsheet navigation in Jetpack Compose.

This library is constantly evolving, and your feedback is invaluable. I encourage you to explore and experiment the library and obviusly
feel free to comment or open issues on GitHub for any questions or problems you encounter. I will be happy to provide assistance and addressing any challenges you might face.

If you find this library useful let the GitHub community know that! Explore, contribute, and star it: https://github.com/stefanoq21/BottomSheetNavigator3

Have a great day!

--

--

Stefano Natali

Senior Android Developer, Kotlin and Compose enthusiast.