Edge to edge Android

Hi, I’d like to add safe area padding for edge to edge android phones, right now it looks like this.

Please notice the white, barely visible status bar on top and gesture navigation crossing the main buttons. If I add in mainActivity.java the system padding by detecting the insets

public class MainActivity extends BridgeActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Enable Edge-to-Edge mode
        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
        EdgeToEdge.enable(this);

        super.onCreate(savedInstanceState);

        // Handle safe area insets dynamically
        ViewCompat.setOnApplyWindowInsetsListener(
                findViewById(android.R.id.content), // Root view of the activity
                (v, windowInsets) -> {

                    // Get insets for gesture navigation and system bars
                    Insets gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
                    Insets systemInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());

                    // Apply padding dynamically (only TOP and BOTTOM)
                    v.setPadding(
                        0,                      // No left padding
                        systemInsets.top,      // Top padding (status bar)
                        0,                      // No right padding
                        gestureInsets.bottom   // Bottom padding (gesture navigation bar)
                    );

                    // Consume the insets so they aren't passed further
                    return WindowInsetsCompat.CONSUMED;
                }
        );
    }
}

then I have issues with the overlay in general since capacitor uses webview, it adds correct padding but doesn’t respect the coloring nor dark mode/ light mode switch … I know about the safe area capacitor plugin but it adds such huge padding to both ios and android and to android phones that don’t even need it (that are not edge to edge). Maybe I configured it incorrectly, how do u guys handle edge to edge?

Thank you

1 Like

I fixed it myself just for the edge to edge phones, if anyone is interested I can post the fix

Hello, I have same problem… can you post the solution?
thank you.

Hi Asko-Dev, I’m also facing issues with edge-to-edge layouts in my Ionic app. Could you please share your fix? It would be really helpful for handling safe area padding properly. Thank you

hey guys yep sorry for the delay… so this is a fix that’s based on the safe area plugin but I wanted more flexibility so …

Step 1
this is the code you put to android/app/src/main/java…/MainActivity.java, if you only had the default few lines there up until now, you can just copy paste completely (just change the com.example.app to your own)

package com.example.app; // CHANGE THIS TO YOUR OWN

import android.content.res.Configuration;
import android.os.Bundle;
import android.view.WindowManager;

import androidx.activity.EdgeToEdge;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.graphics.Insets; // Import this for Insets!
import androidx.core.view.WindowInsetsControllerCompat;

import com.getcapacitor.BridgeActivity;

public class MainActivity extends BridgeActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Enable Edge-to-Edge mode
        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
        EdgeToEdge.enable(this);

        super.onCreate(savedInstanceState);

        // Handle safe area insets dynamically
        ViewCompat.setOnApplyWindowInsetsListener(
                findViewById(android.R.id.content), // Root view of the activity
                (v, windowInsets) -> {

                    // Get insets for gesture navigation and system bars
                    Insets gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
                    Insets systemInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
                    Insets imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime());

                    float density = getResources().getDisplayMetrics().density;

                    int topInset = (int) (systemInsets.top / density);
                    int bottomInset = (int) (gestureInsets.bottom / density);

                    // Handle keyboard visibility and set bottom padding to 0 if keyboard is open
                    int imeHeight = Math.max(0, imeInsets.bottom - systemInsets.bottom);
                    if (imeHeight > 0) {
                        bottomInset = 0; // Ignore gesture insets when keyboard is visible
                    }

                    // Inject CSS variables dynamically
                    setSafeAreaInsets(topInset, bottomInset);

                    // Consume the insets so they aren't passed further
                    return WindowInsetsCompat.CONSUMED;
                }
        );

        // Update status bar and navigation bar appearance
        updateSystemBarAppearance();
    }

    private void setSafeAreaInsets(int top, int bottom) {
        // Set CSS variables dynamically for Ionic to use
        String js = "document.addEventListener('DOMContentLoaded', function() {" +
                "document.documentElement.style.setProperty('--ion-safe-area-top', '" + top + "px');" +
                "document.documentElement.style.setProperty('--ion-safe-area-bottom', '" + bottom + "px');" +
                "});";
        bridge.getWebView().evaluateJavascript(js, null);

    }

    private void updateSystemBarAppearance() {
        // Detect if the device is in dark mode
        boolean isDarkMode = (getResources().getConfiguration().uiMode &
                Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;

        // Get the InsetsController to modify appearance
        WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());

        // Update status bar icons based on mode
        insetsController.setAppearanceLightStatusBars(!isDarkMode); // Light icons in dark mode, dark icons in light mode

        // Update navigation bar icons based on mode
        insetsController.setAppearanceLightNavigationBars(!isDarkMode); // Light icons in dark mode, dark icons in light mode

        // Set transparent bars for edge-to-edge effect
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

        getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
        getWindow().setNavigationBarColor(android.graphics.Color.TRANSPARENT);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // Reapply system bar appearance when theme changes
        updateSystemBarAppearance();
    }
}

So lets go from the top about what it does.

  • First you disable system window defaults which allows you to apply your own decor
  • Then we allow edge to edge for phones that enable it
  • We create
  • Then I use the combination of gesture inset and system inset, some people use just system inset, but for me it turned out to be bit wonky for the gesture based phone, this worked good. So u basically take the paddings for system top (status bar) and gesture bottom (navigation bar), then we take the density and divide the numbers cause the insets u get are scaled based on the density, by dividing it u get the proper insets
  • imeHeight is for when keyboard is opened… then u dynamically set it with an event listener setSafeAreaInsets
  • the other methods are basically to match the color of icons based on the theme that could dynamically change (dark / light) … u switch the icon color

And these lines are important

getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
getWindow().setNavigationBarColor(android.graphics.Color.TRANSPARENT);

those set the transparent colors on navigation bar and status bar which allows the edge to edge decor

Step num 2

Go to android/app/src/main/res/values/styles.xml and make sure in your whatever styles you have there you add these 4 lines

 <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">

        <!-- Dynamic Colors for Light and Dark Modes -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>

        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

    </style>

My entire styles look like this in case u want the whole thing

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">

        <!-- Dynamic Colors for Light and Dark Modes -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>

        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

    </style>

     <!-- No ActionBar variant (used for specific screens or components) -->
    <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
        <!-- Translucent bars for a consistent look -->

        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:background">@null</item>

         <!-- Dynamic Colors for Light and Dark Modes -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@android:color/transparent</item>


        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

    </style>

    <!-- Splash screen style -->
    <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
        <item name="android:background">@drawable/splash</item>
    </style>

</resources>

Step 3 - making sure
Make sure you refer in your AndroidManifest.xml in to your style theme correctly android:theme="@style/AppTheme"

Step 4
And last step is to add this to your main .css file for global css override. The .android class is by default in your project it’s an ionic class. No need to add it on any components. Just paste this to global css and you’re good.

:root {
    --ion-safe-area-top: env(safe-area-inset-top); /* Default for iOS */
    --ion-safe-area-bottom: env(safe-area-inset-bottom); /* Default for iOS */
}

/* Android-specific overrides */
.android {
    --ion-safe-area-top: 0px; /* Start at 0 for Android, updated dynamically via Java */
    --ion-safe-area-bottom: 0px;
}

Look … the code works, you can just copy paste, build it and try to run in a simulator, but I am not a java dev :smiley: it’s possible some of these parts are not even necessary, I know for example some of the methods for the icon color changes in updateSystemBarAppearance are not entirely up to date, but they work …I think I said everything u need, in cause u have issues lemme know, maybe I will recall something I forgot to mention but I think this is it. Good luck :))

Ok, Thankyou for this info.