Targeting API 36 on Android 16 makes edge-to-edge effectively non-optional, and it surfaces a class of failures that won’t appear on older targets. The first time most teams notice is when text fields get covered by the keyboard, padding doubles up across nested layouts, or back navigation starts fighting with the system’s predictive animations.
Android 16 (API level 36) explicitly deprecates and disables the R.attr#windowOptOutEdgeToEdgeEnforcement opt-out for apps targeting 36, so you cannot rely on the old “we’ll deal with edge-to-edge later” approach. (developer.android.com) Predictive back and system animations are also enabled by default (and Android 16 extends predictive back to 3-button navigation). (developer.android.google.cn)
This piece is deliberately not a generic edge-to-edge primer. It’s a catalogue of the failure modes that show up after you flip targetSdk = 36, plus a test matrix and a few automated checks to stop regressions shipping.
What actually changes when you target API 36
Two Android 16 behaviours matter because they turn “optional best practice” into “hard requirement”.
- Edge-to-edge enforcement becomes unavoidable: for apps targeting Android 16 (API 36) and running on Android 16 devices, the opt-out theme attribute
windowOptOutEdgeToEdgeEnforcementis deprecated and disabled. (developer.android.com) - Predictive back system animations are on by default for properly migrated apps, and Android’s documentation is clear that predictive back is enabled by default (with guidance for adopting the modern back APIs). (developer.android.com)
The impact is less “your status bar looks different” and more “every screen that ever assumed system bars were outside its layout bounds can now be wrong”. The IME and insets interaction is where that pain concentrates.
Failure mode 1: IME overlap (keyboard covers the thing you’re typing)
This is the one that gets discovered in production because it only takes one form field near the bottom of the screen.
The common pattern looks like this:
- Your root layout goes edge-to-edge.
- The content is scrollable (RecyclerView, NestedScrollView, Compose LazyColumn).
- A text field near the bottom receives focus.
- The IME appears.
- The focused view does not move into view, or moves but ends up partially under the keyboard.
Why it happens after API 36
Before edge-to-edge is enforced, many apps accidentally “get away with it” because system bars and IME adjustments were effectively masked by fitting system windows or by legacy window soft input behaviour.
Once you’re properly edge-to-edge, the IME is just another inset. If you’re not consuming and applying it intentionally, you’re leaving the layout to chance.
Typical root causes we keep seeing:
- Applying only system bar insets (status/navigation) but not IME insets.
- Applying IME padding to the wrong layer (e.g., adding bottom padding to a scrolling container but not ensuring the focused child is scrolled into view).
- Mixing “automatic” and “manual” behaviour: one layer is doing default insets handling and another layer is doing custom padding.
When the right answer is “do not do this”
- Do not add a hard-coded bottom padding “for the keyboard”. It will be wrong across devices, input methods, split keyboard, floating keyboard, and in landscape.
- Do not apply IME insets to both the root container and the scroll container. That is the fastest route to double padding.
What to test
- Focus a text field at the very bottom of a long form.
- Toggle between gesture nav and 3-button nav.
- Test in portrait and landscape.
- Test with a hardware keyboard connected (IME inset can be zero while focus behaviour still matters).
Failure mode 2: Nested insets get applied twice (or not at all)
Edge-to-edge migrations often involve a patchwork of old and new.
- A legacy Activity root view uses
android:fitsSystemWindows="true". - A newer screen uses
WindowCompat.setDecorFitsSystemWindows(window, false)plus an insets listener. - Compose screens add
systemBarsPadding()orsafeDrawingPadding(). - Material components, app bars, or bottom bars have their own inset behaviour.
On API 36 with edge-to-edge enforced, those layers start to stack.
Symptoms:
- Toolbars sit too low (status bar padding is added twice).
- Bottom navigation floats awkwardly high (navigation bar padding is added twice).
- Content has asymmetric padding (left/right applied but top/bottom not, depending on consumption order).
- Some screens look fine; others are obviously “shrunk”.
The underlying mistake
Insets are not styling; they’re data flowing through your view hierarchy.
Problems happen when:
- You apply insets at a parent and also at children.
- You forget to consume insets after applying them (so children also receive them).
- You rely on framework defaults in one place and explicit insets handling in another.
If you want one rule of thumb: pick a single layer that is responsible for converting insets into padding/margins, and make everything else layout in normal coordinates.
A practical constraint for real apps: mixed Views + Compose
If you have a View-based Activity that hosts Compose fragments (or vice versa), you must decide where insets are handled.
- Handle insets in Views and keep Compose mostly “inset-agnostic”, or
- Make the host view pass through raw insets and handle them in Compose.
Trying to “half-handle” in both is how you get a regression every time a designer adds a new top bar.
Failure mode 3: Gesture navigation conflicts (back gesture vs in-app swipes)
Edge-to-edge doesn’t just affect layout. It changes the interaction boundaries.
Once content draws under the system bars, it’s very common to introduce edge gestures:
- Side drawers.
- Horizontal carousels that start at x=0.
- Map screens with edge pans.
Now add predictive back.
Predictive back introduces a pre-commit back phase where the system shows progress/preview as the user swipes back. Android’s guidance is to migrate to the modern back APIs, and there are explicit priority levels in the platform dispatcher (including PRIORITY_SYSTEM_NAVIGATION_OBSERVER). (developer.android.com)
What breaks in practice
- Your in-app horizontal gesture (carousel, map) competes with back. The system wins often enough that users perceive your gesture as flaky.
- Your custom back handling intercepts when it shouldn’t (confirmation dialogs, webview wrappers), causing the system’s predictive animation to feel wrong.
- Your back stack handling technically works, but the transition looks broken because the system is animating while your UI is “snapping”.
The anti-pattern to avoid
- Do not implement “clever” back logic by intercepting
KEYCODE_BACKor relying on legacyonBackPressedsemantics. Modern Android guidance is to useOnBackPressedCallback/OnBackInvokedCallbackintegrations as appropriate. (developer.android.com)
If you have a navigation framework (Jetpack Navigation, Compose Navigation), keep your custom back logic at the edges: analytics, logging, maybe a very explicit confirmation flow. Anything more tends to conflict with predictive back.
Failure mode 4: Bottom-aligned UI becomes unusable (nav bar + IME + sheets)
The highest-risk screens after API 36 are the ones with multiple bottom layers:
- Bottom navigation
- Persistent bottom CTA
- Snackbar
- Bottom sheet
- IME
You will see:
- Snackbar behind the gesture bar.
- Bottom sheet content clipped.
- CTA pushed up too far (double nav-bar padding), then still covered by IME.
The important detail: these components frequently come from different libraries (Material, your design system, Compose vs Views). Each one may apply its own insets, and they do not coordinate by default.
A test matrix that catches most of this
If you only do one thing, make it this: define a small set of screens and run them through a deterministic matrix.
- Pick 8–12 screens: - Any screen with a scrolling list - Any screen with a bottom bar - Any screen with a form (especially multi-field) - Any screen with a full-screen map or horizontal pan gesture - Any screen with a modal bottom sheet
- Run the matrix (minimum): - Android 16 device/emulator, app targeting API 36 - Gesture navigation - 3-button navigation (predictive back can apply here too) (developer.android.google.cn) - Portrait + landscape - Large font / display size increased
- Explicit IME scenarios: - Focus last field in a long form - Next/Previous navigation between fields - IME action button (Done/Next/Search)
- Explicit back scenarios: - Back from a nested navigation graph - Back while a bottom sheet is half-expanded - Back while a text field is focused and IME is open
Automated checks: cheap guardrails that pay back immediately
You won’t fully automate “this padding looks right” without screenshot testing, but you can still catch the worst regressions with simple checks.
- Add UI tests that:
- Navigate to a form screen
- Scroll to the last field
- Tap the field to open IME
- Assert the field is displayed (not partially clipped)
- Add a lint-like check in code review:
- If a screen applies insets, identify the owning layer (Activity root, Fragment root, Compose root)
- Ensure child layouts are not also applying system bar padding unless intentionally overridden
- Add one screenshot test suite (even small):
- Capture 3–5 key screens in gesture nav + 3-button nav
- Capture with IME open (many teams forget this case)
If you’re resource constrained, prioritise “IME open” coverage. It’s the most user-visible failure and the least likely to be caught by casual manual testing.
Migration approach that doesn’t turn into whack-a-mole
A pattern that tends to work in teams of 20–500 people:
- Decide your insets ownership model - Views-first: insets applied in one root view per screen; Compose uses minimal inset modifiers - Compose-first: insets applied at Compose root; hosting Activity/Fragment does not also pad
- Fix the plumbing before the pixels - Make sure your root window is truly edge-to-edge (no accidental opt-out paths) - Ensure IME insets are observed and applied intentionally
- Only then polish per-screen - Address special cases (bottom sheets, drawers, maps) - Add automated checks for the failure modes above
The mistake is to “fix” individual screens without first deciding where insets are owned; you’ll ship something that looks correct until the next screen introduces a second insets consumer.
Where Codeversols fits (briefly)
If you’re targeting API 36 and you already have a mature UI codebase (mixed Views/Compose, custom design system, multiple navigation stacks), the work is less about edge-to-edge slogans and more about getting a consistent insets contract, validating IME behaviour, and ensuring predictive back doesn’t fight your gestures. That’s the kind of migration we help with at Codeversols: short, focused engineering engagements that leave you with a repeatable test matrix and guardrails, not a one-off patch.
Close
Android 16’s API 36 target changes remove the “we’ll opt out for now” path for edge-to-edge and enable predictive back behaviours that expose weak back handling. (developer.android.com) If you treat this as a cosmetic update, you’ll chase regressions for weeks. If you treat it as a contract change — IME + insets + back navigation as first-class behaviour — you can make it a bounded project with predictable QA and fewer late surprises.



