React Native BLE Plx
react-native-ble-plx is a comprehensive React Native library providing a low-level API for interacting with Bluetooth Low Energy (BLE) devices on both iOS and Android platforms. The current stable version is 3.5.1, with recent releases indicating an active development and maintenance cadence addressing bugs and improvements. It supports core BLE functionalities such as observing the Bluetooth adapter state, scanning for devices, connecting to peripherals, discovering services and characteristics, reading/writing characteristic values, observing notifications/indications, reading RSSI, and negotiating MTU. A key differentiator is its inclusion of an Expo config plugin for easier integration into managed Expo workflows (requiring prebuilding). The library explicitly does not support Bluetooth Classic, inter-phone BLE communication (peripheral mode), device bonding, or beacon technologies. It is built to provide robust control over BLE interactions in React Native applications.
Common errors
-
TypeError: Cannot read properties of null (reading 'getDeviceID') OR Service.getDeviceID()' on a null object reference
cause Attempts to access properties on a null object, often due to race conditions or incorrect cleanup during service/characteristic discovery. Fixed in v3.5.1 for specific cases.fixUpgrade to `react-native-ble-plx@3.5.1` or newer. Implement robust error handling and null checks around device, service, and characteristic access after discovery. Ensure `BleManager.destroy()` is called when no longer needed. -
Promise.reject crash with null arguments
cause A specific bug on Android where internal promise rejections were sometimes made with null arguments, leading to a crash. Fixed in v3.5.1.fixUpgrade to `react-native-ble-plx@3.5.1` or newer to resolve this Android-specific crash. -
Error: You cannot use this package in Expo Go. It requires custom native code.
cause Attempting to run a project using `react-native-ble-plx` in the 'Expo Go' app.fixFor Expo projects, ensure you are using a custom development client or a prebuilt standalone app. Add the config plugin to `app.json` and run `npx expo prebuild`. -
Bluetooth scan fails or no devices found, despite Bluetooth being on.
cause Missing or improperly granted Bluetooth permissions on Android or incorrect `Info.plist` entries on iOS.fixVerify that all necessary Android permissions (`ACCESS_FINE_LOCATION`, `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT` as per API level) are requested and granted at runtime. For iOS, confirm `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` are present and correctly filled in `Info.plist`. -
TypeError: Cannot read properties of undefined (reading 'callMethod') or 'this' context issues on Android.
cause Older versions (prior to 3.2.1) had issues with `this` context for some methods after React Native Fast Refresh on Android.fixUpgrade to `react-native-ble-plx@3.2.1` or newer to benefit from fixes related to `this` context and Fast Refresh on Android.
Warnings
- breaking In version 3.2.0, several methods (`destroyClient`, `cancelTransaction`, `setLogLevel`, `startDeviceScan`, `stopDeviceScan`) were changed to return Promises. Previously, these might have been synchronous or returned void. Existing code that does not await these calls or handle their new Promise return type will break.
- breaking Major version 3.0.0 involved significant internal updates, including `MultiplatformBleAdapter` to 0.2.0, RN bridge config, and dependency updates, alongside fixes for iOS 16 bugs. While not explicitly detailed as breaking, such changes often introduce subtle incompatibilities.
- gotcha As of version 3.4.0, `BleManager` is implemented as a singleton. While this aims to simplify its usage in React components/hooks, it changes the underlying behavior. If you were instantiating `BleManager` multiple times and expecting distinct instances, this will no longer be the case.
- gotcha Using `react-native-ble-plx` with Expo requires the application to be prebuilt (`npx expo prebuild`). It is not compatible with 'Expo Go' because it relies on custom native code. Failing to prebuild will result in runtime errors.
- gotcha Proper Bluetooth permissions are crucial for the library to function. On Android, this involves `ACCESS_FINE_LOCATION` (for Android 11 and below), and `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT`, `ACCESS_FINE_LOCATION` (for Android 12/API 31 and above). iOS requires entries in `Info.plist` for `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription`.
Install
-
npm install react-native-ble-plx -
yarn add react-native-ble-plx -
pnpm add react-native-ble-plx
Imports
- BleManager
const BleManager = require('react-native-ble-plx').BleManager;import { BleManager } from 'react-native-ble-plx'; - State
import { BluetoothState } from 'react-native-ble-plx';import { State } from 'react-native-ble-plx'; - Device
import { Device } from 'react-native-ble-plx';import type { Device } from 'react-native-ble-plx'; - Characteristic
import { Characteristic } from 'react-native-ble-plx';import type { Characteristic } from 'react-native-ble-plx';
Quickstart
import { BleManager, State } from 'react-native-ble-plx';
import { PermissionsAndroid, Platform } from 'react-native';
const manager = new BleManager();
async function requestBluetoothPermissions() {
if (Platform.OS === 'ios') {
return true; // iOS handles permissions differently, usually in Info.plist
}
if (Platform.OS === 'android') {
const apiLevel = Platform.Version;
if (apiLevel < 31) { // Android 11 (API 30) and below
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Location Permission',
message: 'Bluetooth Low Energy requires Location Permission',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} else { // Android 12 (API 31) and above
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
]);
return (
granted['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED
);
}
}
return false;
}
export const scanForDevices = async () => {
const hasPermission = await requestBluetoothPermissions();
if (!hasPermission) {
console.log('Bluetooth permissions not granted.');
return;
}
const subscription = manager.onStateChange((state) => {
if (state === State.PoweredOn) {
console.log('Bluetooth is powered on, starting scan...');
manager.startDeviceScan(null, { allowDuplicates: false }, (error, device) => {
if (error) {
console.error('Scan error:', error);
return;
}
if (device) {
console.log('Found device:', device.name || device.id);
// Example: Stop scan after finding one device
manager.stopDeviceScan();
// subscription.remove(); // Unsubscribe from state changes
}
});
subscription.remove(); // Remove state change listener after successful scan initiation
} else {
console.log('Bluetooth state:', state);
}
}, true);
};
// To stop scanning, typically called after a timeout or finding a device
// manager.stopDeviceScan();
// Don't forget to destroy the manager when your component unmounts or app closes
// manager.destroy();