Sanniv Khare 7 min read

targetSdk 36 to 37 (Android 17): what to change in your code

targetSdk 36 to 37 (Android 17): what to change in your code

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.1 and @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

  1. Raise compileSdk to 37 first (RN 0.87.1 and 0.88.1 already compile against 37 with build tools 37.0.0) and keep targetSdk = 36 for one build. Fix compile errors only.
  2. 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(.
  3. Make each fix while still on 36; every fix below also works there.
  4. Set targetSdk = 37, add ACCESS_LOCAL_NETWORK if you talk to anything on the LAN, and request it at runtime.
  5. 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

Terminal card: diff of react-native libs.versions.toml, targetSdk 36 to 37 and NDK 27.1 to 28.2 in 0.89.0-rc.0

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:

Code card: the ACCESS_LOCAL_NETWORK permission in React Native 0.88.1's debug AndroidManifest.xml

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(, setContentCaptureEnabled or audio focus calls. Native code is loaded through SoLoader.loadLibrary (27 calls).
  • Nine getDeclaredField calls, in ReactTextInputManager and the three scroll views. All read instance fields (mEditor, mCursorDrawableRes, mScroller), so the static-final rule does not apply to them. Whether the lock-free MessageQueue affects anything, we could not tell from source: RN’s MessageQueueThread is its own class, and we found no reflection on android.os.MessageQueue.
  • Orientation locks are app code, not React Native code: if your MainActivity sets android: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.

Need this built? Let’s talk.

We build and run apps for Mac, TV, mobile and the web, and write about what we learn.