Bluetooth Low Energy in React Native: A Production Guide
A production guide to Bluetooth Low Energy in React Native: permissions, scanning, connecting, streaming, background reconnect and testing with hardware.
Full-Stack, Mobile & AI Engineer
Bluetooth demos are easy: scan, connect, read a value, show it on screen. Bluetooth products are hard: Android permission changes, devices that drop connections in pockets, phones that kill background apps, firmware that sends data faster than the UI can draw, and users who expect it all to "just reconnect".
This guide covers React Native Bluetooth development for production apps, specifically Bluetooth Low Energy: choosing a library, permissions on iOS and Android, scanning and connecting, streaming data efficiently, background reconnection, and how to test with real hardware. It's informed by building the app for SuraSole Golf's pressure-sensing smart insoles, which stream data from both feet in real time.
Key takeaways
- Use a maintained BLE library such as
react-native-ble-plx, in a development build (Expo Go can't load it). - Permissions changed in Android 12: request
BLUETOOTH_SCANandBLUETOOTH_CONNECT; older Android versions need location permission to scan. - Stream with notifications, not polling, and negotiate a larger MTU on Android for higher throughput.
- Background reconnection on iOS needs the Bluetooth background mode and careful state restoration; on Android, a foreground service for long sessions.
- Keep high-frequency data out of React state. Buffer it and render per frame, especially for live charts.
BLE in one paragraph
A BLE peripheral (your device) advertises; the phone (the central) scans, connects, then discovers services and characteristics. You read or write characteristics for commands and settings, and subscribe to notifications when the device should push data, such as sensor readings, to the phone. Most product work is designing those characteristics with the firmware team and handling everything that can go wrong around them.
Choosing a library
react-native-ble-plx: a mature, widely used library with a promise-based API, scanning, connections, characteristic monitoring and an Expo config plugin.react-native-ble-manager: another established option with an event-based API and some different platform features.
Both run in development builds. Pick one, wrap it in your own small service module, and keep the rest of the app talking to that module, so a library change later doesn't touch every screen.
Permissions
| Platform | What to request | Notes |
|---|---|---|
| iOS | Bluetooth usage description (NSBluetoothAlwaysUsageDescription) | Explain the device and the benefit, in plain language |
| Android 12+ | BLUETOOTH_SCAN, BLUETOOTH_CONNECT | Mark scanning as not used for location if that's true |
| Android 11 and below | Location permission | Required by the OS to scan for BLE devices |
Ask at the moment the user starts pairing, with a screen that explains why, not on first launch.
Scanning and connecting
import { BleManager, type Device } from "react-native-ble-plx";
const manager = new BleManager();
export function scanForInsoles(onFound: (d: Device) => void) {
manager.startDeviceScan([INSOLE_SERVICE_UUID], null, (error, device) => {
if (error) return handleBleError(error);
if (device) onFound(device);
});
}
export async function connect(deviceId: string) {
manager.stopDeviceScan();
const device = await manager.connectToDevice(deviceId, { requestMTU: 185 });
await device.discoverAllServicesAndCharacteristics();
return device;
}Filter scans by your service UUID so you only see your own devices, stop scanning as soon as you've found the device (scanning drains battery), and handle the case where Bluetooth is turned off with a clear prompt.
Checking whether Bluetooth is on
Before scanning, check the adapter state and react when it changes. With react-native-ble-plx:
const sub = manager.onStateChange((state) => {
if (state === "PoweredOn") {
sub.remove();
scanForInsoles(onFound);
} else if (state === "PoweredOff") {
showTurnOnBluetoothPrompt();
}
}, true); // true: emit the current state immediatelyOther states include Unauthorized (permission denied) and Unsupported. You can't flip Bluetooth on silently: on Android you can ask the system to show its own "turn on Bluetooth" dialog, and on iOS there's no API to toggle it or open the Bluetooth settings page, so show clear instructions instead. Linking.openSettings() opens your app's own settings when the problem is a denied permission.
Streaming data
For sensor data, subscribe to a characteristic's notifications:
device.monitorCharacteristicForService(SERVICE_UUID, PRESSURE_CHAR_UUID, (error, characteristic) => {
if (error || !characteristic?.value) return;
const frame = decodePressureFrame(characteristic.value); // base64 -> typed values
pressureBuffer.push(frame); // not setState
});Practical rules for streams:
- Design compact binary packets with the firmware team, with a sequence number so you can detect dropped frames.
- Negotiate a larger MTU on Android to send more data per packet.
- Decode efficiently and push frames into a buffer or shared value, not React state.
- Render at the display rate: if data arrives faster than the screen refreshes, draw the latest state each frame.
From the field: For SuraSole Golf I built the mobile app for pressure-sensing smart insoles: BLE pairing, background reconnect and real-time plantar-pressure streaming from both feet, rendered as live heat maps and weight-shift charts at 60fps and synced frame by frame against captured swing video. Two devices streaming at once is where buffering, sequencing and rendering discipline stop being optional.
Connection management and reconnecting
Connections drop: devices go out of range, batteries die, phones go into pockets. Plan for it:
- Listen for disconnections and attempt reconnection with backoff, showing the user a calm "Reconnecting…" state.
- Remember the last device so the app can reconnect without a new scan.
- On iOS, enable the Bluetooth background mode (
bluetooth-central) if the app must stay connected or reconnect while in the background, and use state restoration so iOS can relaunch the app for Bluetooth events. - On Android, use a foreground service with the connected-device type for long sessions that must survive the app being backgrounded.
Bluetooth Classic, printers and Arduino modules
Not every device speaks Bluetooth Low Energy, and the difference decides what's possible on iOS:
| Device | Usually uses | iOS | Android |
|---|---|---|---|
| Sensors, wearables, smart home devices | BLE | Yes, with BLE libraries | Yes |
| Receipt and label printers (ESC/POS) | Bluetooth Classic (some models add BLE) | Only BLE models, or MFi-certified accessories | Yes, over Bluetooth serial |
| HC-05 and HC-06 Arduino modules | Bluetooth Classic (serial) | No | Yes |
| HM-10, ESP32, Arduino Nano 33 BLE | BLE | Yes | Yes |
| OBD-II car adapters | Classic or BLE, depending on model | BLE models only | Yes |
BLE libraries like react-native-ble-plx don't handle Classic serial connections; on Android, a library such as react-native-bluetooth-classic does. On iOS, Apple only allows Classic connections to accessories in its MFi program, through the External Accessory framework. So if you're choosing hardware for an app that must work on iPhones, choose BLE models. For printers, check that the exact model supports BLE before you build around it.
A related question that isn't really about BLE: detecting Bluetooth headphones is an audio-routing problem, answered by the platform's audio session APIs rather than a BLE scan. And if you were hoping to share BLE code with a web app, Web Bluetooth works in Chrome on Android and desktop but not in Safari, so it can't reach iPhone users.
Testing with real hardware
- Test on many phones, especially Android devices from several manufacturers, whose Bluetooth stacks differ.
- Test the hard paths: walking out of range and back, turning Bluetooth off and on, killing the app mid-session, low battery on the device.
- Use a BLE simulator or development board for automated tests of your protocol logic, and real devices for everything else.
- Log connection events in production builds (without personal data) so field issues can be diagnosed.
Firmware updates over Bluetooth
Most connected devices eventually need firmware updates, and the phone is usually how they arrive. Plan for it early: agree an update protocol with the hardware team (many chips have vendor update libraries, such as Nordic's DFU), download firmware from your server, verify it before sending, show clear progress, and make the process resumable, because a phone call or a pocket can interrupt it. A failed firmware update is one of the worst experiences a hardware product can give, so test it more than any other flow.
App Store considerations
Apple reviewers usually don't have your hardware. Explain the device in the review notes, include a short video of the full flow, and if possible offer a demo mode that simulates the device. Apple also allows features that depend on specific hardware to be enabled by that hardware without in-app purchase (guideline 3.1.4), which matters for companion apps with premium capabilities.
FAQ
Can I use Bluetooth in Expo?
Yes, with a development build. BLE libraries such as react-native-ble-plx include native code and an Expo config plugin, so they work in Expo projects built with EAS, but not in Expo Go.
Which permissions does BLE need on Android?
On Android 12 and later, request BLUETOOTH_SCAN and BLUETOOTH_CONNECT (and BLUETOOTH_ADVERTISE if the phone advertises). On Android 11 and earlier, scanning requires location permission. Request them in context, when the user starts pairing.
How do I keep a BLE connection alive in the background?
On iOS, enable the bluetooth-central background mode and use state restoration so the system can relaunch your app for Bluetooth events. On Android, run a foreground service with the connected-device type during sessions that must continue in the background.
How fast can React Native receive BLE data?
Fast enough for real-time sensor streams when built well: subscribe to notifications, negotiate a larger MTU on Android, decode efficiently, and keep high-frequency data out of React state. The usual bottleneck is rendering, not Bluetooth.
Next steps
If you're building an app for a Bluetooth device and need pairing, streaming and reconnection to work outside the lab, I've built exactly that and can help. Get in touch, or see how I work as a React Native developer.
Sources
- react-native-ble-plx, GitHub
- Bluetooth permissions, Android Developers
- Core Bluetooth, Apple
- External Accessory framework, Apple
- App Review Guidelines (3.1.4), Apple
Related
Building a Companion App for a Smart Device: Architecture and Pitfalls
How to build a companion app for a smart device or IoT product: onboarding and pairing, data flow, cloud sync, firmware updates, commerce and store rules.
Camera, Media Upload and Video in React Native
Camera and video in React Native in 2026: expo-camera vs VisionCamera, image picking, scanning codes, uploading large media reliably and playing video.
Background Location in React Native That Passes App Review
Background location in React Native and Expo: iOS and Android permissions, location tasks, geofencing, battery, and passing App Store and Play review.
