Skip to content

Implementing Edge-to-Edge Design in Android Apps

Problem Statement

Since Android 15 (API 35) became available, developers have encountered UI issues where app content renders behind the status bar by default. Previously, system bars reserved dedicated space for app content, but now implementations must handle insets programmatically to avoid overlap and inconsistent status bar colors. This creates layout challenges, especially with AppBarLayout and Toolbar elements.

Why Edge-to-Edge Design?

Google now mandates edge-to-edge design implementation for all apps targeting Android 15 and above. This design approach:

  • Maximizes screen real estate
  • Creates a modern, immersive user experience
  • Provides consistent visual behavior across applications
  • Reduces compatibility issues with newer device form factors

Solution: Proper Edge-to-Edge Implementation

1. Declare Edge-to-Edge Mode

In your activity's onCreate, add:

kotlin
WindowCompat.setDecorFitsSystemWindows(window, false)

This is equivalent to calling EdgeToEdge.enable(this) from AndroidX Activity library.

2. Handle Status Bar Insets for AppBar

Apply insets exclusively to your AppBarLayout:

kotlin
ViewCompat.setOnApplyWindowInsetsListener(appBarLayout) { view, windowInsets ->
    val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    view.updatePadding(top = systemBars.top)
    WindowInsetsCompat.CONSUMED
}

3. Set Status Bar Color and Appearance

Configure status bar directly:

kotlin
WindowCompat.getInsetsController(window, window.decorView).apply {
    setAppearanceLightStatusBars(isLightTheme)
    // Do not set status bar color - it's handled by your layout background
}

4. Update Your Layout

Key XML modifications:

xml
<com.google.android.material.appbar.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/toolbar_background" 
    android:fitsSystemWindows="false"> <!-- Disable default fitsSystemWindows -->

    <androidx.appcompat.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"/>
</com.google.android.material.appbar.AppBarLayout>

5. Handle Navigation Bar Insets (Optional)

Avoid bottom UI overlaps:

kotlin
ViewCompat.setOnApplyWindowInsetsListener(fab) { view, windowInsets ->
    val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    view.updateLayoutParams<MarginLayoutParams> { 
        bottomMargin = systemBars.bottom + defaultMargin 
    }
    WindowInsetsCompat.CONSUMED
}

Handling Compatibility Older SDKs

For backward compatibility:

kotlin
@RequiresApi(35)
fun enableEdgeToEdge(activity: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowCompat.setDecorFitsSystemWindows(activity.window, false)
    } else {
        // Legacy edge-to-edge implementation
    }
}

Common Pitfalls and Solutions

1. Status Bar Color Mismatch

Problem: AppBar and status bar show different colors
Solution: Remove setStatusBarColor() calls. Instead:

  • Set AppBarLayout background color
  • Ensure background extends behind status bar

2. Content Hiding Behind System Bars

Problem: UI elements obscured by system bars
Solution: Apply insets only to specific views using setOnApplyWindowInsetsListener, not the root layout.

3. Calculation Errors in Views

Problem: Incorrect height measurements
Solution: Avoid using window.decorView directly. Instead:

kotlin
view.doOnLayout { 
    // Access measured dimensions here 
}

4. Layout Flickering on Cold Start

Problem: Transient layout jumps
Solution: Delay inset handling until measured:

kotlin
if (appBarLayout.isLaidOut) {
    applyWindowInsets()
} else {
    appBarLayout.doOnLayout { applyWindowInsets() }
}

Complete Implementation

kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        WindowCompat.setDecorFitsSystemWindows(window, false)
        
        ViewCompat.setOnApplyWindowInsetsListener(appBar) { view, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.updatePadding(top = systemBars.top)
            WindowInsetsCompat.CONSUMED
        }
        
        WindowCompat.getInsetsController(window, window.decorView).apply {
            setAppearanceLightStatusBars(!isDarkTheme)
        }
    }
}

Important Notes

  1. Test across Android versions: Use Build.VERSION.SDK_INT checks for version-specific logic
  2. Alternative approach: AndroidX Core provides WindowCompat.setDecorFitsSystemWindows() for unified implementation
  3. Design verification: Always test with both light/dark status bar icons
  4. FAB considerations: Apply bottom insets to floating action buttons
  5. Legacy support: Use AndroidX Activity 1.9.0+ for consistent behavior on pre-Android 15 devices

Accessibility Recommendation

Always maintain at least 4.5:1 contrast ratio between status bar icons and your background color

Avoid Common Mistake

Never combine android:fitsSystemWindows="true" with manual insets handling—these approaches conflict

By following this implementation, your app will correctly support edge-to-edge design standards while maintaining backward compatibility with older Android versions.