React Native BLE Background Mode On iOS And Android

React Native BLE Background Mode On iOS And Android

Dec 12, 2025
8 minute read

If your app talks to a wearable or sensor, react native ble background behavior can make or break the experience. In the foreground, BLE feels easy. In the background, both iOS and Android start saving battery, trimming scans, and suspending work.

This guide focuses on what actually works in February 2026: a reliable pattern for background connections and notifications, plus the platform rules you can't code around.

Along the way, you'll see when to use a library like react-native-ble-plx on npm, when you'll need native code, and how to keep App Store and Play Store reviewers happy.

Background BLE expectations, what the OS will and won't do

"Background" means different things depending on state:

  • Backgrounded: the app is not visible but still resident.
  • Suspended: iOS freezes your process, Android may stop CPU time except for allowed work.
  • Terminated: user force quits, or the OS kills your process.

On iOS, background BLE is designed for connected peripherals and service based use cases, not for constant discovery. Scanning can work, but it's limited and often filtered. Connection maintenance and characteristic notifications are the usual win.

On Android, the big shift is Doze and background execution limits (API 26+), plus stricter permission and foreground service rules (API 29 to 35+). You can do reliable background BLE, but long running work should run under a foreground service with a visible notification.

If you're new to the React Native BLE ecosystem, this React Native BLE Manager guide is a helpful comparison point for how the common libraries map to native stacks.

A minimal cross-platform pattern that stays reliable

!Clean vector diagram of React Native BLE app flow from foreground to background transition, showing JS layer calling BleManager.startDeviceScan, native bridges to iOS CoreBluetooth and Android BluetoothLeScanner, and background states with icons for suspended app, maintained connections, and notifications. _Diagram of a practical BLE flow from React Native to native iOS and Android layers, created with AI._

A good mental model is "JS orchestrates, native keeps the radio alive." The smallest pattern that holds up looks like this:

  1. React Native JS/TS starts scans, connects, and subscribes to notifications.
  2. A native BLE manager owns the long lived connection state.
  3. Android uses a foreground service to keep scanning or reconnecting.
  4. Events bubble back to JS through an emitter, so UI and storage stay in sync.
  5. You persist the last known peripheral IDs, so reconnect does not depend on broad scanning.

A compact React Native example (TypeScript style, keep it in a shared module):

  • const manager = new BleManager()
  • manager.onStateChange(s => s === 'PoweredOn' && start(), true)
  • `manager.startDeviceScan([SERVICE_UUID], { allowDuplicates: true }, onScan)`
  • const sub = device.monitorCharacteristicForService(SVC, CHAR, onNotify)

For background friendliness, prefer reconnecting to known devices:

  • const device = await manager.connectToDevice(deviceId, { autoConnect: true })
  • await device.discoverAllServicesAndCharacteristics()

To receive native background events (Android service, iOS restoration), keep the JS side event driven:

  • const emitter = new NativeEventEmitter(NativeModules.BleBackground)
  • const sub = emitter.addListener('bleEvent', payload => store.dispatch(...))

If JS must stay awake to keep BLE working, the design is already fragile. Push durability into native, then notify JS.

iOS: CoreBluetooth background mode with state restoration (iOS 15 to iOS 18)

!Clean modern vector diagram split-screen comparing React Native BLE background operation on iOS (left: CoreBluetooth, state restoration) and Android (right: BluetoothLeScanner, foreground service, Doze). Includes app states, permissions, OS restrictions, battery impact, and recommended patterns. _Split view of iOS and Android background BLE constraints and recommended architecture, created with AI._

Required iOS settings (don't skip these)

In Xcode, enable Background Modes and check the Bluetooth option (commonly "Uses Bluetooth LE accessories"). In Info.plist, include a clear usage string, such as NSBluetoothAlwaysUsageDescription, that matches your real use.

Two iOS realities matter most:

  • Background scanning is limited, and results are often filtered.
  • Connected peripherals can keep delivering notifications, and iOS can relaunch your app for BLE events if you use restoration correctly.

State restoration in native code (Swift and Objective-C)

If you write a native module, initialize your central manager with a restore identifier.

Swift:

  • `central = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "ble.restore"])`

Objective-C:

  • `self.central = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{ CBCentralManagerOptionRestoreIdentifierKey: @"ble.restore" }];`

Then implement restoration:

  • Swift: centralManager(_:, willRestoreState:)
  • Obj-C: - (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict

On the React Native side, react-native-ble-plx supports restoration hooks. Keep the identifier stable across installs and upgrades. Reference the official react-native-ble-plx documentation when wiring restoreStateIdentifier and the restore callback.

Scanning and discovery tips that reduce iOS pain

Filter your scan by service UUID whenever possible:

  • `manager.startDeviceScan([SERVICE_UUID], null, callback)`

Also make sure your peripheral advertises the service UUID in the primary advertising packet, not only in scan response. Otherwise, iOS may not surface it when the app is backgrounded.

Finally, build for stalls. When a scan seems "stuck," stop and restart it after a short delay, and reconnect based on saved IDs instead of hoping a broad scan will keep firing.

App Store review note: only enable Bluetooth background mode if your app truly needs it (for example, a medical sensor or a continuous wearable link). Explain the user benefit in review notes and in the permission copy.

Android: Foreground service scanning and reconnect (API 29 to 35+)

Android gives you more freedom than iOS, but it demands a clear contract: long running BLE work should be visible to the user.

Permissions by Android version (what usually breaks)

  • API 29 to 30 (Android 10 to 11): scanning often implies location. Many apps still need ACCESS_FINE_LOCATION to see scan results on older devices.
  • API 31+ (Android 12+): request BLUETOOTH_SCAN and BLUETOOTH_CONNECT at runtime. You can add android:usesPermissionFlags="neverForLocation" to BLUETOOTH_SCAN, but test OEM devices because behavior varies.
  • API 33+ (Android 13+): notifications need runtime permission in many cases (POST_NOTIFICATIONS), which matters because your foreground service must show one.
  • API 34 to 35+ (Android 14 to 15): declare the right foreground service type in the manifest. Also expect stricter background starts.

Kotlin foreground service pattern (minimal but real)

In your service's onStartCommand, start in the foreground before scanning:

  • startForeground(NOTIF_ID, buildNotification())
  • scanner.startScan(filters, settings, scanCallback)

When you connect, keep reconnection logic native side:

  • gatt = device.connectGatt(this, false, gattCallback, TRANSPORT_LE)

Then send events back to React Native (via a native module or headless bridge):

  • reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("bleEvent", map)

Scanning all day is a battery tax and can get throttled. Scan in bursts, reconnect from saved IDs, and stop the service when you're done. For real world notes from other teams, see this ble-plx background mode discussion, then adapt it to your policy and UX.

Play Store compliance: a foreground service notification must be clear, user facing, and dismissible only when the work stops. If you hide the notification or keep it running "just because," reviewers and users will push back.

Background capabilities and the ways they usually fail

Here's the quick comparison most teams end up rediscovering.

| Platform | Background scan | Background connect | Notify/indicate in background | Required settings | Typical failure modes | | --------------------- | ------------------------------------------- | ---------------------------- | ----------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | iOS 15 to 18 | Limited, filtered, best with service UUID | Usually OK for known devices | Strong, best path | Background Mode for Bluetooth, stable restoration ID, usage strings | Scan callbacks stop, no results without UUID filter, restoration not triggered | | Android API 29 to 30 | Possible, often tied to location | OK, but Doze affects timing | Strong once connected | Location permissions on many devices, battery optimization handling | Empty scan results, OEM battery kills app, delayed reconnect | | Android API 31 to 35+ | Possible, but best under foreground service | Good with service | Strong once connected | BLUETOOTH\_SCAN/CONNECT runtime, foreground service notification | ScanFailed, service killed if not foreground, missing notification permission |

Takeaway: iOS prefers connection plus notifications, Android prefers foreground service plus clear UX.

Troubleshooting: the logs that save hours

!Modern flat illustration of two mobile developers troubleshooting BLE background issues: one at a desk with iOS simulator showing CoreBluetooth logs in Xcode, the other with Android Studio and Logcat filtering BluetoothGatt logs. Laptops side by side with coffee mug, simple tools, soft lighting, clean white background. _Two devs comparing iOS CoreBluetooth logs and Android Logcat Bluetooth logs, created with AI._

On iOS, watch for state and restoration signals:

  • Confirm centralManagerDidUpdateState reaches poweredOn.
  • Check willRestoreState gets called after relaunch.
  • Use macOS Console and filter for "CoreBluetooth" and "CBCentralManager."

If notifications stop in background, validate the peripheral keeps the connection alive and that you re-subscribe after reconnect.

On Android, Logcat tells you what the stack thinks:

  • Filter tags like BluetoothGatt, BtGatt, and scanner related logs.
  • Use adb logcat | grep -i bluetooth when you need a wide net.

When a device "vanishes," test these in order: screen off, Battery Saver on, app backgrounded, then app swiped away. Each state is a different fight.

Conclusion

Background BLE is less like streaming music and more like keeping a walkie talkie on standby. iOS rewards state restoration and connected notifications, while Android rewards foreground services and clear user intent. Build around those rules, and your React Native app stops feeling random. If your current design depends on endless background scans, rethink it before you ship.