Skip to content

Changing Status Bar Color in Android 15+

Problem Statement

When migrating apps to Android 15 (API 35+), developers encounter a breaking change: the traditional window.statusBarColor property is deprecated and no longer functional. The new requirement is to draw backgrounds using the WindowInsets API. This shift aims to enforce edge-to-edge designs but poses challenges when applying custom status bar colors (e.g., static colors like RED or GREEN) in a backward-compatible way.


For Android 15+, handle status bar coloring by:

  1. Listening to WindowInsets changes
  2. Setting the decor view's background color
  3. Applying top padding to avoid content overlap

Kotlin Implementation

kotlin
import android.graphics.Color
import android.os.Build
import android.view.View
import android.view.Window
import android.view.WindowInsets

fun setStatusBarColor(window: Window, targetColor: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { // Android 15+
        window.decorView.setOnApplyWindowInsetsListener { view, insets ->
            // Extract the status bar height
            val statusBarHeight = insets.getInsets(WindowInsets.Type.statusBars()).top
            
            // Apply background color and padding
            view.setBackgroundColor(targetColor)
            view.setPadding(0, statusBarHeight, 0, 0)
            
            // Return the original insets
            insets
        }
    } else { // Android 12-14 (API 31-34)
        @Suppress("DEPRECATION")
        window.statusBarColor = targetColor
    }
}

Usage: Call setStatusBarColor(window, Color.RED) in your activity's onCreate.

Java Equivalent

java
View decorView = getWindow().getDecorView();
decorView.setOnApplyWindowInsetsListener((v, insets) -> {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
        Insets statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars());
        v.setBackgroundColor(ContextCompat.getColor(this, R.color.red));
        v.setPadding(0, statusBarInsets.top, 0, 0);
    }
    return insets;
});

Key Details

  • Edge-to-Edge Requirement: Ensures compatibility with gesture navigation and system UI overlays
  • Backward Compatibility: The Build.VERSION_CODES check maintains functionality on older OS versions
  • Custom Colors: Directly pass any color resource or hex value, bypassing theme-based colors

Temporary Workaround (Avoid Long-Term)

Opt out of edge-to-edge enforcement using windowOptOutEdgeToEdgeEnforcement. This restores statusBarColor behavior but is marked for deprecation. Use only for short-term mitigation:

In themes.xml (API 35+)

xml
<!-- res/values-v35/themes.xml -->
<resources>
    <style name="Theme.App" parent="Theme.Material3.DayNight">
        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
        <item name="android:statusBarColor">@color/your_custom_color</item>
    </style>
</resources>

Manifest Application/Activity Tag

xml
<application
    android:theme="@style/Theme.App"
    android:windowOptOutEdgeToEdgeEnforcement="true">

DANGER

Warning: This is a temporary fix. Google will remove this property in future Android releases, potentially breaking your app. Prefer the WindowInsets solution for production apps.


Best Practices Explained

  1. Why WindowInsets?
    Android 15 enforces proper background management behind system bars. Drawing directly via insets conforms to edge-to-edge design standards and prevents visual artifacts with transient system bars (e.g., notifications).

  2. Padding Requirement
    Setting top padding matching the status bar height prevents critical UI elements (like buttons or titles) from being obscured by the status bar. Without this, content might render under the status bar area.

  3. Avoid DecorView Hacks
    Solutions like .setBackgroundColor() without padding adjustments (e.g., window.decorView.setBackgroundColor(Color.RED)) cause overlapping content issues. Always account for insets.


Jetpack Compose Alternative

For declarative UIs:

kotlin
setContent {
    CompositionLocalProvider(
        LocalStatusBarStyle provides YourAppColors.statusBarStyle()
    ) {
        Box(
            Modifier
                .fillMaxSize()
                .windowInsetsTopHeight(WindowInsets.statusBars)
                .background(Color.Red)
        ) {
            // Your app content
        }
    }
}

Key modifier: .windowInsetsTopHeight(WindowInsets.statusBars) ensures the status bar area is reserved and colored.


Final Recommendation

Use the WindowInsets approach for Android 15+ compatibility. It aligns with modern UI standards, avoids deprecation risks, and provides reliable behavior across devices. Reserve the windowOptOutEdgeToEdgeEnforcement workaround for temporary migrations only.