Skip to content

Instantly share code, notes, and snippets.

@Khazbs
Last active May 8, 2024 16:18
Show Gist options
  • Save Khazbs/1c18d21f33d18ba64fd8530832c1d118 to your computer and use it in GitHub Desktop.
Save Khazbs/1c18d21f33d18ba64fd8530832c1d118 to your computer and use it in GitHub Desktop.
If enableEdgeToEdge sucks for your Android activity's Compose UI, try this instead!

Go Edge-to-Edge on Android in a More Customizable Way

Google currently recommends calling enableEdgeToEdge() in order to make your Compose UI go edge-to-edge. However, the result it yields may be slightly different from how you really want your app's activity to look. For example, an unwanted semi-transparent scrim may appear behind the navigation bar. Don't worry, it's actually possible to tweak most of this stuff!

Here I show the three steps I take to go edge-to-edge. You can try it yourself and see if you like it too! If you have any questions, suggestions for improvement or your own preferred solutions, feel free to leave a comment down below, I'm open and curious to read them!

1. Set Decor Fits System Windows to false

This is done to tell Android that our activity wants to draw content where system bars usually are.

Instead of using enableEdgeToEdge(), I still use the good old WindowCompat.setDecorFitsSystemWindows(window, false) in my activity's onCreate method. Why? Because enableEdgeToEdge() draws a visible scrim behind the navigation bar, even if you tell it to make it transparent, and I don't want that!

// MainActivity.kt

// ...
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ===== Hello-hello, the line below! =====
    WindowCompat.setDecorFitsSystemWindows(window, false)
    // ===== Bye-bye, the poor next line! =====
    // enableEdgeToEdge()
    // ...

Some folks suggest using android:fitsSystemWindows="false" in the activity's manifest attributes, but it never made a dent for what I could see, so I ditched it. Instead, I add another manifest attribute: android:windowSoftInputMode="adjustResize", which is a different thing entirely, but I'll thank myself later when I realize that otherwise my software keyboard covers up half the activity,

2. Tweak system bars' appearance in your XML themes

In the style resources, also known as themes, it's possible to set desired system bar colors and disable the system scrims. Wait, what? More scrims? Yes, there are also system scrims for the system bars, and I don't want them either!

So first I go to the default theme and set the system bar colors to transparent.

<!-- values/themes.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="Theme.ArchitectureTestApp" parent="android:Theme.Material.Light.NoActionBar">
        <!-- ===== These two lines below ===== -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
    </style>
</resources>

Then I do the same thing for the dark night theme. And yes, a separate values-night theme XML is a must, because otherwise the app's default splash screen will be light, which will burn the users' eyes!

<!-- values-night/themes.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- For the users' eyes' sake please make sure it's Material, not Material.Light -->
    <style name="Theme.Main" parent="android:Theme.Material.NoActionBar">
        <!-- ===== These two lines below again ===== -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
    </style>
</resources>

Then I disable the system scrims. Since the style items that offer such options are only available since Android API level 29 (Android 10 "Quince Tart"), I'm doing this in values-v29.

<!-- values-v29/themes.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="Theme.Main" parent="android:Theme.Material.Light.NoActionBar">
        <!-- ===== Yep, these four lines below ===== -->
        <item name="android:enforceStatusBarContrast">false</item>
        <item name="android:enforceNavigationBarContrast">false</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
    </style>
</resources>

Same goes for the dark values-night-v29 theme.

<!-- values-night-v29/themes.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Again, Material, not Material.Light, remember? -->
    <style name="Theme.Main" parent="android:Theme.Material.NoActionBar">
        <!-- ===== Mm-hmm, these four lines below again, you get it ===== -->
        <item name="android:enforceStatusBarContrast">false</item>
        <item name="android:enforceNavigationBarContrast">false</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>
    </style>
</resources>

Alright then! At this point I might want to play around with some more specific theme tweaks depending on the design, but the main job is done: the bars are now transparent and my activity can draw whatever I want behind them.

Except for one more little thing I also gotta do…

3. Set the system bars' content brightness

I want to make sure that the foreground content of the system bars is properly visible on top of my activity: when the dark theme is enabled in Compose UI, I want the system bars' foreground content to appear light, and when the light theme is enabled, I want the foreground content to appear dark.

Since I made the system bars transparent, it is now my Compose theme's responsibility to tell the insets controller if the background is light or dark, so that the system knows whether the bars' foreground content should be dark or light.

And guess what? This is exactly what the following code does, and I use it right in my app's theme composable.

// Theme.kt

// ...
@Composable
fun MainTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit, // Obsessed with trailing commas!
) {
    // ...

    // ===== These nine lines below =====
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            val window = (view.context as Activity).window
            val insets = WindowCompat.getInsetsController(window, view)
            insets.isAppearanceLightStatusBars = !darkTheme
            insets.isAppearanceLightNavigationBars = !darkTheme
        }
    }
    // ===== These nine lines above =====

    MaterialTheme(
        // ...
        content = content, // Still obsessed, thanks for asking!
    )
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment