How to Use BLE in React Native to Connect RPM Devices (Basic Setup)

Table of contents
- What You'll Need
- Step 1: Set Up Your React Native Project
- Step 2 : Setting Up Permissions
- Step 3: Scanning For Devices
- Step 4 : Connecting to a Device
- Step 4: Discover Services and Characteristics
- Step 5: Accessing Characteristics to Read or Subscribe to Data
- Step 6: Read , Write & Monitor BLE Characteristics

“A clean, step-by-step React Native BLE tutorial to connect and read data from RPM devices using react-native-ble-plx.”
Bluetooth Low Energy (BLE) is the go-to protocol for wirelessly connecting devices like fitness trackers, smartwatches, and Remote Patient Monitoring (RPM) tools with mobile apps. In React Native, the react-native-ble-plx
library provides a powerful yet flexible way to implement BLE functionality.
But let’s be honest…
I know the pain. You go through the docs, scroll endlessly, and yet you’re still sitting there wondering:
“Which functions do I even use? How do I start? Where do I go from here?”
Yeah… been there.
That’s exactly why I wrote this blog — to cut through the confusion and give you a simple, basic implementation with the least amount of code needed to connect your React Native app to an RPM (Remote Patient Monitoring) device using BLE.
No fluff. No deep dives. Just the cleanest path to getting your app talking to a device. Let’s go.
What You'll Need
A React Native project set up on your computer
A BLE-enabled blood pressure monitor
A smartphone with for testing
Step 1: Set Up Your React Native Project
First, make sure you have a React Native app set up. If not, you can start fresh using:
npx react-native init BLEExample
#or if you use yarn
yarn add react-native-ble-plx
Navigate into your project directory:
cd BLEExample
Now install the BLE library:
npm install --save react-native-ble-plx
#yarn
yarn add react-native-ble-plx
Step 2 : Setting Up Permissions
Before your app can start scanning for RPM devices over Bluetooth, you need to get those pesky permissions sorted.
Android Permissions
On Android, BLE requires location and Bluetooth permissions. And depending on the Android version, the required permissions change. So yeah... if you want your app to work across all Android versions, you'll need to handle all these cases cleanly.
npm install --save react-native-permissions
#yarn
yarn add react-native-permissions
Then, make sure to add the permissions to your AndroidManifest.xml
:
<!-- Required for BLE -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
iOS Permissions
iOS keeps it a little saner. You don't need react-native-permissions
unless you want more control—but you must update your Info.plist or your app will crash or get rejected.
Add these to ios/YourApp/Info.plist
:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to medical devices.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access for BLE to function properly.</string>
Then run:
cd ios && pod install
Step 3: Scanning For Devices
Next job is to scan for available BLE devices nearby. The react-native-ble-plx
library gives you a BleManager
class to handle all BLE operations—kind of like your Bluetooth command center.
import { BleManager } from 'react-native-ble-plx';
const manager = new BleManager();
Now, to scan :
manager.startDeviceScan(null, null, (error, device) => {
if (error) {
console.error('Scan error:', error);
return;
}
// Filter (Optional) : Only show devices with a name
if (device && device.name) {
console.log('Found device with name:', device.name);
}
});
setTimeout(() => {
bleManager.stopDeviceScan();
console.log('Stopped scanning');
}, 10000); // 10 seconds
};
null, null
means you're scanning for all BLE devices (you can filter with UUIDs if needed).device.name
might benull
for some legit devices; in that case, try checkingdevice.localName
if needed.You can store these devices in state if you’re displaying them in UI (e.g.,
useState
in React).
Step 4 : Connecting to a Device
const connectToDevice = async (deviceId) => {
const connectedDevice = await bleManager.connectToDevice(deviceId);
console.log('Connected to device:', connectedDevice.name || connectedDevice.localName);
};
Step 4: Discover Services and Characteristics
When you connect to a device, you don't instantly know what it can do. You have to ask the device to reveal:
What services it offers
What characteristics are inside each service
What kind of operations you can perform on them
This is what await connectedDevice.discoverAllServicesAndCharacteristics()
does. Without this, you can't:
Read RPM values
Write settings
Subscribe to real-time data
const discoverDeviceServices = async (device) => {
const discoveredDevice = await device.discoverAllServicesAndCharacteristics();
const services = await discoveredDevice.services();
for (const service of services) {
const characteristics = await service.characteristics();
// You can now read, write, or subscribe to characteristics if needed
}
return discoveredDevice;
};
Step 5: Accessing Characteristics to Read or Subscribe to Data
You’ll need:
A Service UUID (which contains your desired data)
A Characteristic UUID (the exact point to read from or subscribe to)
( These are usually given by your device’s documentation or vendor.)
const readOrSubscribeToData = async (device, serviceUUID, characteristicUUID) => {
const services = await device.services();
for (const service of services) {
if (service.uuid === serviceUUID) {
const characteristics = await service.characteristics();
for (const char of characteristics) {
if (char.uuid === characteristicUUID) {
// To read once
const value = await char.read();
const decodedValue = Buffer.from(value.value, 'base64').toString('utf-8');
// OR: To subscribe for live updates
char.monitor((error, characteristic) => {
if (characteristic?.value) {
const liveValue = Buffer.from(characteristic.value, 'base64').toString('utf-8');
// Do something with liveValue
}
});
}
}
}
}
};
💥 And BAM — That’s It!
Your app can now read data from RPM devices.
Step 6: Read , Write & Monitor BLE Characteristics
isNotifiable
: Real-time BLE Updates
This is for live health data like:
SpO2 readings from an oximeter
Heart rate from a chest strap
Step count from a fitness tracker
manager.monitorCharacteristicForDevice(
device.id,
characteristic.serviceUUID,
characteristic.uuid,
(error, char) => {
if (char?.value) {
const decodedValue = Buffer.from(char.value, 'base64').toString('utf-8');
const data = JSON.parse(decodedValue);
setOximeterData({ spo2: data.spo2 || '', pulseRate: data.pulseRate || '' });
}
}
);
isReadable
: One-Time Value Fetch
Device name
Firmware version
Battery level
connectedDevice.readCharacteristicForService(
characteristic.serviceUUID,
characteristic.uuid
).then((char) => {
const decodedValue = Buffer.from(char.value, 'base64').toString('utf-8');
});
isWritable
: Sending commands to Your Device
If you need to send data to the device — like changing a setting or triggering a measurement — then you’ll use isWritableWithResponse
or isWritableWithoutResponse
.
await characteristic.writeWithResponse('your_base64_encoded_value');
Note: Not every characteristic supports writing — and some expect specific formats, so always check your device documentation.
For more advanced features, detailed configuration, and platform-specific nuances, you can refer to the official documentation here:
You can also check out my implementation : GitHub
Subscribe to my newsletter
Read articles from Astha Niharika directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
