DEV Community

Cover image for React Native + Android 16: The SDK Upgrade That Looks Easy Until Your UI Breaks
Muhammad haris baig
Muhammad haris baig

Posted on

React Native + Android 16: The SDK Upgrade That Looks Easy Until Your UI Breaks

Moving from target SDK 35 to 36 looks like a simple Gradle change. In a real React Native app, the build may be the easiest part.

If your React Native Android app is still targeting API 35, Android 16 (API 36) should be on your migration roadmap.

Google Play requires apps to target Android 16 (API 36) for updates starting August 31, 2026.

The configuration changes are relatively straightforward:

  • compileSdkVersion: 35 → 36
  • targetSdkVersion: 35 → 36
  • buildToolsVersion: 35.x → 36.x
  • Android Gradle Plugin: 8.7.x → 8.9.1+
  • Gradle: 8.9 → 8.11.1+
  • minSdkVersion: remained 24
  • No React Native major version upgrade was required just for this SDK bump

The first build succeeded.

Then the real work started.


The "Easy" SDK Upgrade

The first mistake teams can make is assuming:

"The app builds, so the migration is done."

Not quite.

An Android SDK upgrade can change platform behavior without changing a single line of your React Native JavaScript.

You may suddenly find:

  • Headers moving under the status bar
  • Notches overlapping content
  • Back buttons becoming difficult to tap
  • Keyboard behavior changing
  • Forms behaving differently
  • Existing navigation issues surfacing during testing

This is why I see target SDK migrations as behavioral migrations, not just build configuration changes.

Our approach was simple:

Get the app compliant with the new target SDK with the smallest possible change set.

We didn't combine the migration with a React Native upgrade, navigation rewrite, UI redesign, or complete safe-area refactor.

First make the platform migration. Then fix the platform-specific behavior it exposes.


The Real Breakage: Edge-to-Edge

The biggest issue we encountered was Android's edge-to-edge behavior.

With Android 16, edge-to-edge is enforced, and windowOptOutEdgeToEdgeEnforcement is no longer a reliable solution.

This changes an assumption many React Native apps have quietly depended on:

"The operating system will leave space for my content below the status bar."

With edge-to-edge, your application can draw behind system bars.

That's fine when the UI is intentionally designed for it.

It's a problem when your existing UI assumes the system will automatically keep content away from the status bar.

Our first symptoms were:

  • Headers partially hidden
  • Content underneath the notch
  • Back buttons becoming harder to tap
  • Authentication screens looking incorrect
  • Attempted fixes creating excessive spacing

At this point, the migration became more than an SDK upgrade.

It became a UI architecture problem.


Don't Fix Safe Areas Screen by Screen

The obvious solution is to add safe-area padding to every screen.

Be careful.

If your navigation container already handles the top inset and individual screens add their own safe-area padding, you can end up with:

  • Double padding
  • Large empty spaces
  • Misaligned headers
  • Inconsistent spacing

The approach that worked better for our architecture was to apply the Android top inset once at the root router/navigation layer.

Instead of every screen asking:

"Do I need to add status bar padding?"

We established a single responsibility:

The root navigation layer owns the Android top inset.

The principle is simple:

Insets should have one clear owner.

This makes the layout hierarchy easier to reason about and avoids solving the same platform problem repeatedly across individual screens.


Status Bar Color Is Part of the UI

Another issue appeared on authentication screens.

A fixed white status-bar filler looked fine on white screens.

Then we opened a colored authentication screen.

Suddenly, there was a white strip at the top.

The overlap was fixed.

The UI was still wrong.

The lesson is that system-bar handling isn't only about spacing. It's also about visual context.

When handling edge-to-edge, think about two questions:

  1. Where should the content start?
  2. What should appear behind the system bar?

An authentication screen may need a branded background, while the main application may use white.

A hardcoded white filler isn't a universal solution. Simply making everything transparent isn't necessarily the answer either.

This is another reason centralized root-level handling is easier to maintain than manually adding spacers across dozens of screens.


The Keyboard Problem: adjustResize Isn't Enough

The second major issue was keyboard behavior.

We had screens using the familiar combination of:

  • KeyboardAvoidingView
  • behavior="padding"
  • windowSoftInputMode="adjustResize"

Historically, this pattern has worked well for many React Native applications.

But edge-to-edge changes what you can expect from classic window resizing.

On Android 15+ / API 35+, and continuing into Android 16, relying solely on adjustResize is no longer sufficient in edge-to-edge scenarios.

The result can look like this:

User taps an input
        ↓
Keyboard opens
        ↓
Window doesn't resize as expected
        ↓
Content remains behind the keyboard
        ↓
KeyboardAvoidingView can't fully solve it
Enter fullscreen mode Exit fullscreen mode

This isn't necessarily a React Native bug.

It's a change in how Android handles the application's window and system insets.

The solution we used was to handle the IME (keyboard) inset natively in MainActivity for SDK 35+:

  • Detect the keyboard using WindowInsets
  • Read the bottom IME inset
  • Apply the required bottom padding to the root content

The idea is straightforward:

Keyboard opens
      ↓
Android reports IME WindowInset
      ↓
MainActivity receives the bottom inset
      ↓
Root content gets bottom padding
      ↓
React Native UI moves above the keyboard
Enter fullscreen mode Exit fullscreen mode

This is more reliable than trying to solve every form with increasingly complex ScrollView or KeyboardAvoidingView styles.

One recommendation from this migration:

Don't randomly change your layouts because the keyboard is covering content.

We tried layout-level approaches that technically moved the content but also disturbed centering and spacing.

If the UI was correct before the platform change, fix the platform behavior at the platform layer whenever possible.


What to Test After API 35 → 36

Don't stop after confirming that the app builds and installs.

Run a focused regression test.

System UI

Test:

  • Status bar and notch behavior
  • Edge-to-edge
  • Navigation bar
  • Back gesture
  • Header alignment
  • Touch targets near system bars

Keyboard

Test:

  • Login and authentication forms
  • Long forms
  • Multiple text inputs
  • Password fields
  • Keyboard opening and closing
  • Content near the bottom of the screen

Core Android Features

Test:

  • Push notifications
  • Deep links
  • Camera
  • Gallery
  • File selection
  • Permissions

Navigation

Test:

  • Nested navigators
  • Tab and stack navigation
  • Back button and gesture
  • Deep links into nested screens

Devices

At minimum, test:

  • Android 16 / API 36
  • One older supported Android version
  • A device with a notch or cutout

And run a quick iOS sanity check.

React Native shares JavaScript layout code across platforms, so Android-specific changes should not accidentally affect iOS.


The Biggest Lesson

Android SDK upgrades are becoming less about changing a number in Gradle and more about understanding how Android is evolving.

The API 36 migration is a good example.

The configuration change is relatively small.

The behavioral impact can be much larger.

The most important lesson from our migration was to avoid over-engineering the solution.

We didn't need to redesign the entire application.

We needed to identify where Android 16 changed existing assumptions and fix those assumptions at the correct layer.

For us, that meant:

  • Centralizing Android top inset handling at the root router
  • Avoiding duplicate safe-area padding
  • Matching system-bar backgrounds to the screen context
  • Handling keyboard IME insets natively for newer Android versions
  • Keeping the iOS safe-area path unchanged
  • Avoiding layout hacks that introduced new spacing problems

The goal wasn't to rewrite the app.

The goal was to make the smallest architectural changes necessary to keep the app stable on the new platform.

Five Takeaways

1. Target SDK upgrades are behavioral migrations.
A successful build doesn't mean your migration is complete.

2. Android 16 edge-to-edge needs intentional UI handling.
Don't assume the system will keep your content away from system bars.

3. Give insets one clear owner.
Centralized handling is usually safer than adding safe-area fixes to every screen.

4. Keyboard behavior needs platform-aware handling.
With edge-to-edge, don't rely blindly on adjustResize and KeyboardAvoidingView.

5. Keep the migration surgical.
Get Play compliance done first, then fix UI and runtime issues one by one.

Android 16 won't be the last platform change React Native developers have to deal with.

The engineers who handle these migrations well won't be the ones who simply know which Gradle value to change.

They'll be the ones who understand where JavaScript ends, where React Native begins, and where Android platform behavior takes over.

That's the real skill behind maintaining production-grade React Native applications.

Top comments (0)