Moving an app from targetSdk 36 to 37 (Android 17) breaks seven kinds of code we would expect in an ordinary app: dev or LAN networking without the new ACCESS_LOCAL_NETWORK permission, background audio without a while-in-use foreground service, OTP reading from SMS_RECEIVED, MODE_BACKGROUND_ACTIVITY_START_ALLOWED, setContentCaptureEnabled(false) as a privacy screen, large-screen orientation locks, and reflection into static finals or MessageQueue. React Native is already partway there: we diffed the npm packages on 7 Nov 2026, and RN 0.88.1 ships the local-network permission for debug builds, while 0.89.0-rc.0 moves its own build to targetSdk = "37".
Why we did this: BMDB on Android TV, Peepal and Shopkeeper all target API 36 now, and Play’s API 36 deadline (31 August 2026, extension to 1 November) has passed, so 37 is the next bump. We wanted one checklist that covers the Kotlin side and the React Native side.
How we checked
- Behaviour changes: Google’s apps targeting Android 17 page (last updated 31 Oct 2026) and all apps page (28 Oct 2026), read on 7 Nov 2026. Every row below links back.
- Native sources:
npm pack react-native@0.87.1,@0.88.1and@0.89.0-rc.0, unpacked and diffed (gradle/libs.versions.toml,ReactAndroid/src). - Samples: we cloned Google’s platform-samples (commit 1ffa0db, 3 Nov 2026) and Now in Android (2982e63, 20 Oct 2026) and grepped for the affected APIs.
- Not run on an API 37 emulator or device yet. Nothing here is a runtime result.
The changes that apply when you target 37
| Change | What breaks | Fix | RN apps? |
|---|---|---|---|
| Local network permission | LAN connections (dev server, printers, TVs, local APIs) without ACCESS_LOCAL_NETWORK |
Declare and request it, or use a system device picker | Debug: RN 0.88.1 handles Metro. Release: yours |
| Background audio hardening | Playback, audio focus and volume calls from the background; focus returns AUDIOFOCUS_REQUEST_FAILED |
Foreground service with while-in-use capability | Yes, if a library plays audio |
| OTP protection for SMS | OTP messages withheld from SMS_RECEIVED_ACTION for three hours |
SMS Retriever or SMS User Consent | Yes, banking apps |
Background activity launch from IntentSender |
MODE_BACKGROUND_ACTIVITY_START_ALLOWED |
MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE |
Only native code |
setContentCaptureEnabled deprecated |
setContentCaptureEnabled(false) no longer disables Content Capture |
FLAG_SECURE |
Yes, banking screens |
| Large-screen restrictions ignored | On sw600dp+, the Android 16 opt-out from ignoring orientation, resizability and aspect ratio is gone | Make layouts resizable | Yes, portrait-locked apps |
| Static final fields unmodifiable | Reflection writes throw IllegalAccessException; JNI SetStatic<Type>Field crashes |
Stop writing static finals | Check native libs |
Lock-free MessageQueue |
Reflection on its private members | Use public APIs | Check native libs |
| Widget bitmap memory limit | updateAppWidget throws IllegalArgumentException over 6 x width x height bytes |
Downsample, RGB_565, URI images | Only with widgets |
| Certificate Transparency on by default | Connections to certs not in CT logs | Use publicly logged certs | Yes |
| Read-only native libraries | System.load() of a writable .so throws UnsatisfiedLinkError |
Load from read-only locations | Only custom loaders |
| Contacts PII and strict SQL | ACCOUNT_NAME, ACCOUNT_TYPE removed from ContactsContract.Data |
Join RawContacts on RAW_CONTACT_ID |
Only contact pickers |
Also on the page: ECH is on when the library and server support it (configurable with <domainEncryption> in the Network Security Configuration), password characters are hidden when a physical keyboard is used, BluetoothSocket.read() on RFCOMM returns -1 when the socket closes, and custom InputConnections should report text change types for accessibility.
Two changes apply to every app on Android 17, whatever you target: per-app keystore limits (50,000 keys for apps targeting 37) and the coming end of implicit URI grants for ACTION_SEND, ACTION_SEND_MULTIPLE and ACTION_IMAGE_CAPTURE in Android 18 (all apps page). Add the grant flags now; the StrictMode check detectImplicitUriPermissionGrant() finds the places.
The port, step by step
- Raise
compileSdkto 37 first (RN 0.87.1 and 0.88.1 already compile against 37 with build tools 37.0.0) and keeptargetSdk = 36for one build. Fix compile errors only. - Grep your own code and your native dependencies for the APIs in the table:
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,setContentCaptureEnabled,SMS_RECEIVED,requestAudioFocus,screenOrientation,resizeableActivity,getDeclaredField,System.load(. - Make each fix while still on 36; every fix below also works there.
- Set
targetSdk = 37, addACCESS_LOCAL_NETWORKif you talk to anything on the LAN, and request it at runtime. - Run on an API 37 emulator and a sw600dp emulator (tablet or foldable) with StrictMode on, then a release build on a real device.
What broke, with before and after code
Background activity starts from a PendingIntent
// Before: allowed from the background on targetSdk 36
val options = ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
// After: allowed only while the app is visible
val options = ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
)
Google’s own samples still have one: samples/user-interface/appwidgets/.../remote_compose/StoreButton.kt in platform-samples sets MODE_BACKGROUND_ACTIVITY_START_ALLOWED under @Suppress("DEPRECATION"). Of the 16 patterns we grepped, that and one ContactsContract use were the only hits in platform-samples apart from 13 widget files (the widget memory limit); Now in Android had none, and it still targets 36.
Hiding a screen from Content Capture
// Before: no longer disables Content Capture on targetSdk 37
contentCaptureManager?.setContentCaptureEnabled(false)
// After (from Google's page)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
For an RN banking app this lives in MainActivity.onCreate, or in a small native module if only some screens should be secure.
Reading OTPs
// Before: a BroadcastReceiver on SMS_RECEIVED_ACTION (withheld for 3 hours on 37)
registerReceiver(otpReceiver, IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION))
// After: SMS Retriever (no SMS permission needed)
SmsRetriever.getClient(context).startSmsRetriever()
The SMS Retriever flow needs the app hash in the message and its own receiver for SmsRetriever.SMS_RETRIEVED_ACTION. If you use an RN OTP library, check which of the two it wraps before you bump.
Local network access
<!-- After: in android/app/src/main/AndroidManifest.xml, release builds too -->
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK"/>
The permission sits in the NEARBY_DEVICES group, so users who already granted a nearby-devices permission are not asked again. From JavaScript, RN 0.88.1 already exposes PermissionsAndroid.PERMISSIONS.ACCESS_LOCAL_NETWORK.
What React Native already did

React Native’s own build moves to targetSdk 37 and NDK 28.2 in 0.89.0-rc.0; 0.87.1 and 0.88.1 compile against 37 but target 36.
The 0.88.1 package already has the Android 17 dev-server fix:

Declared in src/debug and src/debugOptimized only, so release builds do not inherit it.
LocalNetworkPermissionUtil.kt asks for the permission from ReactActivityDelegate before loading the app, only when ReactBuildConfig.DEBUG is true and the device is on API 37 or later; adb reverse loopback is exempt. If your release app talks to a LAN device (a TV, a printer, a local API), the permission and the prompt are yours to add.
Other things the diff and grep of ReactAndroid/src/main in 0.88.1 showed:
- No
setRequestedOrientation,screenOrientation,resizeableActivity,System.load(,setContentCaptureEnabledor audio focus calls. Native code is loaded throughSoLoader.loadLibrary(27 calls). - Nine
getDeclaredFieldcalls, inReactTextInputManagerand the three scroll views. All read instance fields (mEditor,mCursorDrawableRes,mScroller), so the static-final rule does not apply to them. Whether the lock-freeMessageQueueaffects anything, we could not tell from source: RN’sMessageQueueThreadis its own class, and we found no reflection onandroid.os.MessageQueue. - Orientation locks are app code, not React Native code: if your
MainActivitysetsandroid:screenOrientation="portrait", that is the line large screens will now ignore.
Not tested yet
We have not run any of this on an API 37 emulator or device. The next step on our side is a Peepal debug build on an API 37 emulator over the LAN (to see the prompt), then a release build with targetSdk 37 on a sw600dp emulator to see what the ignored portrait lock does to the layout.



