Edge-to-Edge Is No Longer Optional — Android 16 Migration Guide.

Edge-to-Edge Is No Longer Optional — Android 16 Migration Guide.

Starting with Android 16 (API 36), edge-to-edge is no longer optional. Google has removed the opt-out, and if your app isn’t ready, it’s going to break especially on Android 15 and up.

The old fallback:

android:windowOptOutEdgeToEdgeEnforcement="true"

is now completely ignored. Your app is expected to draw behind the system bars by default. If you’re not already handling that, you’re going to start seeing overlapping content, broken layouts, and a seriously degraded user experience.

What’s Going to Break? 😬

If your UI wasn’t built with edge-to-edge in mind, here’s what you’ll start noticing:

  • Content pushed under system bars — buttons and text half-hidden
  • Bad insets — keyboard overlaps, bottom bars floating in weird places
  • Inconsistent layouts — some screens padded right, others completely broken
  • Hardcoded spacing issues — static 16.dp paddings won’t cut it anymore

It’s not just an Android 16 problem — these layout issues already show up on Android 15, and the new enforcement just locks it in.

How to Fix It — Step by Step

1. Enable Edge-to-Edge in Your Activity

Update your Activity to use enableEdgeToEdge():

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.light(
Color.TRANSPARENT, Color.TRANSPARENT
)
)
super.onCreate(savedInstanceState)
setContent { MyApp() }
}

Using .light() here implicitly sets window.isNavigationBarContrastEnforced = false internally. Dive into the source code and you’ll see it’s all handled under the hood no more hacks.

2. Stop Using Accompanist’s SystemUiController

This used to be the way:

val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
isNavigationBarContrastEnforced = false
)
}

But that’s outdated now. enableEdgeToEdge() replaces this entirely with official support.

3- Use Scaffold Properly

Scaffold already handles a lot of insets for you padding for nav bars, status bars, keyboards, etc.

Here’s the right way to use it:

Scaffold { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
// Your screen content
}
}

Or if you’re only concerned about the bottom inset:

Modifier.padding(bottom = innerPadding.calculateBottomPadding())

⚠️ Be Careful When Using bottomBar

If you’re using Scaffold(bottomBar = …), the padding returned in innerPadding already includes the height of both the navigation bar and your bottomBar.

That means if you apply innerPadding to your content, you’ll end up with extra space.

The Better Approach

Instead of wrapping each screen with a Scaffold, wrap the entire NavHost in a single, top-level Scaffold.

That way:

  • You manage padding only once.
  • You avoid duplicated spacing under the bottom bar.
  • You don’t have to refactor every screen just to fix insets.
Scaffold() { innerPadding ->
NavHost(
modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()),
navController = navController,
startDestination = ...
) {
...
}
}

4. Replace ModalBottomSheetLayout

If you’re still using ModalBottomSheetLayout, you’ve probably seen a visual bug where part of the backdrop remains visible when the sheet is hidden. That won’t hold up anymore.

Use Material 3’s BottomSheetScaffold instead. It handles insets correctly, supports the right gestures, and doesn’t require layout hacks.

BottomSheetScaffold(
scaffoldState = rememberBottomSheetScaffoldState(),
sheetContent = { /* ... */ }
) { innerPadding ->
// Main content
}

If you’re building custom sheets or backdrops, consider migrating to your own implementation using this as a base or align it with your design system

Migration Options

Option 1: Full Migration

  • Add a top-level Scaffold around your NavHost.
  • Pass innerPadding down to screens.
  • Replace all legacy bottom sheets.
  • Remove all hardcoded paddings.

Pros:

  • Clean layout structure.
  • Fewer bugs.
  • Easier to maintain going forward.

Cons:

  • You’ll need to refactor most of the UI layer.
  • Some custom logic (e.g. backdrops, sheets) may need to be rewritten.

Option 2: Quick Patch

  • Wrap only the broken screens in a Scaffold.
  • Fix paddings there.
  • Leave the rest of the app as-is for now.

Pros:

  • Faster.
  • Less risk right now.

Cons:

  • Adds layout inconsistency.
  • You’ll need to fix it properly later anyway.

What I Suggest

If your codebase is large or you’re mid-release, go with a staged approach:

Phase 1 — Quick Fix

  • Patch the screens that are breaking.
  • Use Scaffold and apply innerPadding.
  • Disable the nav bar contrast overlay.

Phase 2 — Clean Migration

  • Add a top-level Scaffold around the NavHost.
  • Stop using ModalBottomSheetLayout.
  • Let the system control padding — no more hardcoded values.
  • Test edge cases: keyboard, split-screen, dark mode, gestures, etc.

Dev Guidelines Going Forward

  • Always use Scaffold as the layout root.
  • Don’t wrap each screen with Scaffold if you already have a global one.
  • Pass innerPadding from Scaffold — never hardcode values.
  • Use BottomSheetScaffold for sheets.
  • If you’re using a design system, make sure backdrops, sheets, and modals respect system insets.

References


Edge-to-Edge Is No Longer Optional — Android 16 Migration Guide. was originally published in Google Developer Experts on Medium, where people are continuing the conversation by highlighting and responding to this story.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
announcing-the-data-commons-gemini-cli-extension

Announcing the Data Commons Gemini CLI extension

Next Post

Google Checks: Simplifying Privacy Compliance

Related Posts