Edge-to-edge and predictive back: what Android 16 asks of a normal screen
A screen-by-screen teardown of what breaks when an Android app targets API 35 and 36, from the status bar to the keyboard, with the Compose and React Native fixes we use.

Two Android changes have been arriving in steps since 2024, and Android 16 finished both. Apps that target API 35 draw edge-to-edge by default, behind the status bar and the navigation bar. Apps that target API 36 can no longer opt out of that, and they get predictive back: the system animates back gestures for you and stops calling the old back hooks.
Google Play's target API requirements mean every maintained app crosses these lines. So instead of restating the release notes, this teardown walks one ordinary screen, an inbox with a top bar, a list, a floating action button, a bottom sheet, a text field and a dialog, and lists what each change does to each part.
What the platform now does
| Your app targets | Edge-to-edge | Predictive back |
|---|---|---|
| API 34 or lower | Only if you opt in, for example with enableEdgeToEdge(). | Only if you opt in with android:enableOnBackInvokedCallback="true". On Android 13 and 14 users only see the animations with a developer option. |
| API 35 (Android 15) | On by default on Android 15 devices. Status bar and gesture navigation bar are transparent; 3-button navigation gets a translucent scrim. You can still opt out with windowOptOutEdgeToEdgeEnforcement. | The developer option is gone. Back-to-home, cross-activity and cross-task animations run for apps that have opted in. |
| API 36 (Android 16) | Enforced on Android 16 devices. windowOptOutEdgeToEdgeEnforcement is deprecated and disabled; it only still works when your app runs on an Android 15 device. | On by default on Android 16 devices. onBackPressed() is not called and KEYCODE_BACK is not dispatched. Temporary opt-out: android:enableOnBackInvokedCallback="false". |
The teardown
| Screen element | What breaks | Fix |
|---|---|---|
| Status bar | Your top bar or header starts at y = 0, so the title sits under the clock and battery icons. | Pad the top for systemBars/statusBars. Material 3 TopAppBar does this for you. Make sure status bar icons stay readable on your header colour. |
| Top app bar | Usually fine in Material 3. Custom headers break: either they lose their background behind the status bar or the content is pushed under it. | Draw the background to the very top, pad only the content. One surface, one colour, from the screen edge down. |
| List under the gesture bar | The last row hides behind the navigation bar and cannot be tapped, or it ends with a strip of unused padding. | Apply the bottom inset as content padding on the list, not as padding on its container, so rows scroll under the bar but the last one ends above it. In Views, clipToPadding="false". |
| Floating action button | Sits on top of the gesture handle or overlaps the 3-button bar. | Offset it by the systemBars bottom inset. Inside a Compose Scaffold, use the floatingActionButton slot rather than placing it yourself. |
| Bottom navigation | Labels sit in the gesture area; taps conflict with the home swipe. | Material 3 NavigationBar and BottomAppBar pad for the bottom inset. A custom tab bar needs the same. |
| Bottom sheet | Sheet content ends under the navigation bar; drag handles fight the system gesture. | Material 3 ModalBottomSheet applies the bottom inset. In custom sheets, pad for the bottom inset and keep drag targets out of the systemGestures area. |
| Keyboard (IME) | The text field or send button is hidden behind the keyboard, because an edge-to-edge window is not shrunk for you. | Keep android:windowSoftInputMode="adjustResize" so the app receives IME insets, and pad the bottom of the screen with them. |
| Dialogs | Centred dialogs are fine. Full-screen dialogs either leave a band at the top or draw their content under the bars. | Make the dialog's own window edge-to-edge (Views: WindowCompat.enableEdgeToEdge(dialog.window) in onStart()) and handle insets inside it. |
| Custom back handling | A form that warns about unsaved changes on back stops warning, because onBackPressed() is never called on Android 16. | Move to OnBackPressedCallback or Compose BackHandler / PredictiveBackHandler, enabled only while there is something to protect. |
| Landscape and cut-outs | Content slides under the camera cut-out when the phone is rotated. | Include displayCutout in the insets you apply to content, or use the combined safeDrawing set in Compose. |
Compose: the default screen
In Compose most of the work is choosing the right container. Scaffold hands you the insets as innerPadding but does not apply them; you pass them to the content. Material 3 app bars, navigation bars and the modal bottom sheet handle their own sides.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // same look on Android 14 and older
setContent { InboxScreen(messages = sampleMessages) }
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InboxScreen(messages: List<Message>) {
Scaffold(
topBar = { TopAppBar(title = { Text("Inbox") }) }, // pads for the status bar
) { innerPadding ->
LazyColumn(
modifier = Modifier.consumeWindowInsets(innerPadding),
contentPadding = innerPadding, // rows scroll under the bars, last row stays reachable
) {
items(messages) { MessageRow(it) }
}
}
}
Passing innerPadding as contentPadding rather than as a Modifier.padding is the difference between rows that scroll behind a translucent bar and rows that stop dead at an invisible line. The keyboard needs its own treatment, usually on the composer at the bottom of a chat or form:
// Message composer pinned to the bottom of a chat screen
Row(
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets.ime.union(WindowInsets.navigationBars))
) { /* text field + send button */ }
Predictive back
Predictive back changes what you are allowed to do on back. The system now plays a preview animation while the user drags: the app shrinking to reveal the home screen, or the previous activity sliding in. It can only do that if it knows in advance that your app will not intercept the gesture.
So the rule is simple: register a back callback only when you really need one, and disable it the rest of the time. An always-enabled callback, for example one that logs analytics, silently turns off the back-to-home animation for your whole app. If you need to observe back without consuming it, Android 16 adds PRIORITY_SYSTEM_NAVIGATION_OBSERVER.
// Compose: follow the gesture, then commit or reset
PredictiveBackHandler(enabled = sheetOpen) { progress: Flow<BackEventCompat> ->
try {
progress.collect { event -> sheetOffset = event.progress }
sheetOpen = false // gesture committed
} catch (e: CancellationException) {
sheetOffset = 0f // gesture cancelled
}
}
// Views: only enable the callback while you really need to intercept back
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() { showDiscardDialog() }
}
onBackPressedDispatcher.addCallback(this, callback)
callback.isEnabled = hasUnsavedChanges
For in-app transitions, such as closing a sheet or a detail pane, PredictiveBackHandler gives you a stream of progress values so your own animation can follow the finger and then commit or reset. That is where the gesture starts to feel native rather than tolerated.
React Native and Expo
React Native 0.81 moved the default Android target to API 36, so every app on a current version is edge-to-edge on Android 16 and gets predictive back by default. Version 0.81 also deprecated the built-in <SafeAreaView>, which only ever worked on iOS, and added an edgeToEdgeEnabled Gradle property that applies the same layout on older Android versions.
- Insets: use
react-native-safe-area-context. Wrap the app inSafeAreaProvider, then use itsSafeAreaViewwith anedgesprop for simple screens, oruseSafeAreaInsets()when you need the numbers for list padding or a custom tab bar. - Older Android versions: set
edgeToEdgeEnabled=trueinandroid/gradle.propertiesso Android 14 and older users see the same layout as Android 16 users. Before 0.81, the community libraryreact-native-edge-to-edgefilled this gap. - Back: React Native's
BackHandlerkeeps working for most cases. Custom native code that overridesonBackPressed()is what breaks. - Expo: from SDK 54, edge-to-edge is always on and cannot be disabled. Predictive back shipped disabled by default in SDK 54 and is turned on with
android.predictiveBackGestureEnabledin app config; check the notes for your SDK version. - Keyboard: test every screen with a text field. When
KeyboardAvoidingViewstarts misbehaving on an edge-to-edge app, we usually reach forreact-native-keyboard-controller.
import { FlatList } from 'react-native';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
function Inbox() {
const insets = useSafeAreaInsets();
return (
<FlatList
data={messages}
renderItem={renderRow}
contentContainerStyle={{ paddingTop: insets.top, paddingBottom: insets.bottom + 16 }}
/>
);
}
export default function App() {
return <SafeAreaProvider><Inbox /></SafeAreaProvider>;
}
How we test it
- Test with gesture navigation and 3-button navigationThey have different bar heights and different scrims. Switch in system settings; it takes ten seconds.
- Test on Android 15 and Android 16Behaviour differs for apps targeting API 36, and an old opt-out only works on 15.
- Scroll every list to the endThe last row must be fully visible and tappable above the navigation bar.
- Open the keyboard on every screen with a fieldThe focused field and the primary button must stay visible.
- Rotate with a cut-out deviceLandscape is where
displayCutoutbugs live. - Swipe back slowly from every screenYou should see the preview animation. If you do not, find the callback that is enabled when it should not be.
None of this is glamorous work, but it is the difference between an app that looks native on a current Android phone and one that looks a few years old.
Sources
- Behavior changes: apps targeting Android 16 or higher · Android Developers
- Display content edge-to-edge in views · Android Developers
- Set up edge-to-edge in Compose · Android Developers
- Use Material 3 insets · Android Developers
- Add support for the predictive back gesture · Android Developers
- React Native 0.81 · React Native
- Expo SDK 54 · Expo
- react-native-safe-area-context · GitHub


