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 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 :))