Handling StatusBar colors when using ModalBottomSheets in Jetpack Compose.

I started translating one of my apps in Jetpack Compose and while the overall experience has been quite good, doing some things turned out to be quite tricky. One of those things was to properly set up a ModalBottomSheet.

As you can see with just a basic implementation of ModalBottomSheetLayout the scrim gets applied everywhere but on the StatusBar. While this can be fine in some configurations, in mine it looked totally off.

What do we do to solve it then?

We will make our app extend below the Toolbar which will be set to transparent. Using this strategy we allow ModalBottomSheetLayout to handle the scrim (and consequentially the Toolbar color) itself. We are also going to do something quite similar to the NavigationBar so the sheet can slide flawlessly into the screen.

Let's dig into this!

First of all let's add the dependency we need.

Then we are going to create our sheet as you normally would.

If we were to run this code now, it would behave as shown before. Let's fix it.

The first thing we want to do is calling setDecorFitsSystemWindows before setContent { ... } in our activity like this:

After doing this our app won't care about insets anymore and will, as a result, display content below the StatusBar and the NavigationBar.

Now we need to make our StatusBar transparent. In order to do this we can either edit themes.xml or rely on another accompanist library named System UI Controller. Let's go through both solutions:

themes.xml :

Inside your main theme use android:statusBarColor to achieve what we need.

System UI Controller:

Let's add the dependency we need.

Then in our theme composable we need to create an instance of SystemUiController and call setStatusBarColor on it.

At this point the situation is as follows:

The red stroke outlines where the app's Toolbar is. Far from ideal right?

Let's create a Spacer with the height of the StatusBar. It will be our StatusBar background, and our app will have full control of it.

Inside our ModalBottomSheetLayout composable let's add a Column as the root element and inside it let's place our spacer as follows (you may arguably want to place it inside the Toolbar composable and that's fine too).

If you run your app now it will still display exactly as before. That's because we need to provide informations to our composable about the insets, otherwise .statusBarHeight() won't work at all (or actually will work but will set the height to 0). So in your activity inside setContent { ... } let's provide the insets.

If you run the app now you should see this:

And if you now open the sheet the scrim will correctly apply even to the StatusBar! Hurray!! 🎉🎉

We are not done just yet though. If you look closely at our app in the current state you will notice the content is getting cut. That's because since we set setDecorFitsSystemWindows to false our content is being displayed not only under the StatusBar but also under the NavigationBar.

In order to fix this we are going to make the NavigationBar transparent and add another Spacer, this time at the bottom, which will act as a background.

As before we can either set the transparency in themes.xml or in the theme composable.


Something to be mentioned is that Android will try to keep a decent amount of contrast between the navigation icons and the bar background. So if you try to set your NavigationBar to be fully transparent on a white background with white icons the system will draw a darker semi-transparent toolbar instead. If you are targeting API ≥ 27 you can use android:windowLightNavigationBar in order to display it on a white background. It's also worth mentioning that on API ≥ 29 there is android:enforceNavigationBarContrast which allows you to get rid of this limitation altogether (even though I don't think it is a good idea generally speaking.).

System UI Controller:

Yeah yeah, I know. We just set the contrast enforcing to false after saying it is not a good idea. This was done because it appears to be the only way to do it. I think the function which is supposed to calculate the contrast between the icons color and the background color fails when the background color is set to transparent. This could be by design because with a transparent background the actual color is going to depend on what content is shown, hence being dynamic and not evaluable once and for all.

It's worth mentioning that you could use systemUiController.setSystemBarsColor to set both the StatusBar and the NavigationBar to be transparent at the same time.

Let's head back to the ModalBottomSheetLayout composable content and add another Spacer. The resulting code will be as follows.

I want to point out some things here. First of all look at the sheetContent ModalBottomSheetLayout parameter. We apply some padding using .navigationBarsWithImePadding()to the sheet itself, otherwise it will be displayed under the NavigationBar. Then inside the Scaffold body we provide some padding to the Column, which is the padding given to us by the Scaffold, otherwise it will display the body's content under the bottomBar.

This is what we have now:

What's good about this solution is that not only we have solved the problem with the StatusBarbut we have also solved some problems with the NavigationBar, indeed now if we were to change the sheet color we would notice it properly slides from the bottom of the screen up and consequentially colors the NavigationBar.

To be a bit more reasonable we could create a new Scaffold component which does what we have described until now.

Let me specify some differences there are between this implementation of Scaffold and the androidx.compose.material.Scaffold one. First of all our doesn't support Drawers. To be honest, this is because I didn't find a way to make it work. Moreover it takes two additional parameters, statusBarColor and navigationBarColor, I think they are self explanatory enough.

Just a normal dude passionate about computer science.