Every smartphone we use today has Bluetooth built into it. As a result, mobile app developers often build applications that require Bluetooth to work.
In this article, we will learn how to integrate the Bluetooth connection feature — specifically, Bluetooth Low Energy — in our React Native application. We will also learn how to handle Bluetooth permissions and how to set up BLE on the target device.
Jump ahead:
- What is BLE?
- Prerequisites for our React Native BLE project
- Installing
react-native-ble-manager
- Building the BLE manager for our React Native app
- Using the
enableBluetooth()
method - Using the
start()
method - Using the
startScan()
method - Using the
getConnectedPeripherals()
method - Connecting to and disconnecting from a BLE device
- Reviewing our full React Native BLE application
What is BLE?
Bluetooth has been a useful technology standard for file sharing, as well as an upgrade from the days of infrared. This technology allows us to connect seamlessly to these devices without using unnecessary cables. It is also cost-effective as it is relatively cheap to implement.
Many devices today have Bluetooth built in, including mobile devices, laptops, headsets, cars, speakers, heart rate monitors, and so on. These devices can use Bluetooth for things like file sharing, music sharing, or connecting to external speakers, cars, and other Bluetooth-compatible devices.
However, one of the many demerits of Bluetooth is that it drains the battery life of your device, particularly if it remains turned on. Bluetooth Low Energy (BLE) is a version of Bluetooth that was designed to fix this problem.
BLE is based on Bluetooth and offers many of the same features, but uses less energy or power. While it’s not ideal for transferring large files and isn’t as fast as Bluetooth Classic, it is particularly useful for devices that depend on long battery life rather than being connected to power continuously.
BLE is designed specifically for short-range connections and communications, and it helps preserve device battery life. This type of connection is common in the health, fitness, and security industries.
Prerequisites for our React Native BLE project
Before we begin, you should have these installed on your local computer:
- React Native
- npm — I’m using v8
- Node.js — I’m using the v18
- A physical device — e.g., a smartwatch, heart rate monitor, etc.
No matter which version of npm or Node you use, everything we do should still work fine in your project.
Installing react-native-ble-manager
For this tutorial, we will be using the react-native-ble-manager
and a smartwatch. To install, use either of the code commands below:
/* npm */ npm i --save react-native-ble-manager /* yarn */ yarn add react-native-ble-manager
Next, go into your AndroidManifest.xml
file and add the following configuration. This is to allow some permissions that we will be looking at shortly:
/* android/app/src/main/AndroidManifest.xml */ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.reactnativeblemanager"> /* add the permissions below before the aplication tag */ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <application> ...... </application> </manifest>
The permissions we added above allow our application to connect to our phone’s location, scan for nearby BLE devices, and connect to them. Location and Bluetooth permissions are runtime permissions, so we request these permissions at runtime along with declaring them in our manifest.
To use the react-native-ble-manager
package, we will be importing the following modules:
import { Platform, NativeModules, useColorScheme, NativeEventEmitter, PermissionsAndroid, } from 'react-native'; import BleManager from 'react-native-ble-manager'; import {Colors} from 'react-native/Libraries/NewAppScreen';
Let us go over what the modules above do.
The native Platform
allows us to check the OS version of our device or devices and perform relevant operations dependent on the OS.
With NativeModules
, we can access a native platform API — either for Android or iOS — that is not available to us by default. We can then perform functions based on that platform.
The useColorScheme
Hook allows us to check the current theme the user prefers — either dark or light mode — and then style the application based on the returned value.
The NativeEventEmitter
module allows us to receive or listen to events. It works like the web addEventListener
where you invoke a function based on a particular event.
The PermissionsAndroid
API allows us access to Android’s permission model. As long as the permission is added in the AndroidManifest.xml
file, this API can grant such permissions. However, certain permissions that can be seen as harmful and dangerous will prompt the user to accept them before they can be granted.
The Colors
API lets us style our application by giving us access to colors. We can use this together with the useColorScheme
Hook for a better styling guide.
Building the BLE manager for our React Native app
Now that we have seen and understood the modules and native APIs we will be using, let us now go ahead with building the BLE manager for our React Native application.
Copy the code below into your App.js
file:
import React, {useState, useEffect} from 'react'; import { Text, View, Platform, StatusBar, ScrollView, StyleSheet, Dimensions, SafeAreaView, NativeModules, useColorScheme, TouchableOpacity, NativeEventEmitter, PermissionsAndroid, } from 'react-native'; import BleManager from 'react-native-ble-manager'; import {Colors} from 'react-native/Libraries/NewAppScreen'; const App = () => { const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; return ( <SafeAreaView style={[backgroundStyle, styles.mainBody]}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <ScrollView style={backgroundStyle} contentContainerStyle={styles.mainBody} contentInsetAdjustmentBehavior="automatic"> <View style={{ backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, marginBottom: 40, }}> <View> <Text style={{ fontSize: 30, textAlign: 'center', color: isDarkMode ? Colors.white : Colors.black, }}> React Native BLE Manager Tutorial </Text> </View> <TouchableOpacity activeOpacity={0.5} style={styles.buttonStyle}> <Text style={styles.buttonTextStyle}>Scan Bluetooth Devices </Text> </TouchableOpacity> </View> </ScrollView> </SafeAreaView> ); }; const windowHeight = Dimensions.get('window').height; const styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', height: windowHeight, }, buttonStyle: { backgroundColor: '#307ecc', borderWidth: 0, color: '#FFFFFF', borderColor: '#307ecc', height: 40, alignItems: 'center', borderRadius: 30, marginLeft: 35, marginRight: 35, marginTop: 15, }, buttonTextStyle: { color: '#FFFFFF', paddingVertical: 10, fontSize: 16, }, }); export default App;
In the above code, we have configured the relevant modules and added some text and a button. When we click the button, we want to scan for nearby Bluetooth devices.
We are using the useColorScheme
Hook to check for the current theme and then assign the color we want our text and button to be using the Colors API:
Using the enableBluetooth()
method
Before using react-native-ble-manager
, you first have to make sure your Bluetooth is turned on, and then initialize the BleManager
. You can either turn on your Bluetooth from your device menu or through the application.
react-native-ble-manager
has a method called enableBluetooth()
that gives you access to the device’s Bluetooth and returns a promise.
Note that all the methods in react-native-ble-manager
return a promise.
If your phone’s Bluetooth is not turned on, the application will prompt you to grant it access to turn your Bluetooth on:
Also, we have to grant our application access to the phone’s location to enable us to perform our scan:
Take a look at the code we used to grant these permissions below:
useEffect(() => { // turn on bluetooth if it is not on BleManager.enableBluetooth().then(() => { console.log('Bluetooth is turned on!'); }); if (Platform.OS === 'android' && Platform.Version >= 23) { PermissionsAndroid.check( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ).then(result => { if (result) { console.log('Permission is OK'); } else { PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ).then(result => { if (result) { console.log('User accept'); } else { console.log('User refuse'); } }); } }); } }, []);
Using the start()
method
Before we can begin scanning for nearby BLE devices, we will have to first start our react-native-ble-manager
package. BleManager
provides us with a start()
method that allows us to turn our package on.
Inside your useEffect
Hook, add the following code block so that when our application loads, the package will be turned on:
useEffect(() => { // start bluetooth manager BleManager.start({showAlert: false}).then(() => { console.log('BleManager initialized'); }); }, []);
If react-native-ble-manager
starts successfully, it logs a message in your console telling you that BleManager
is initialized.
Using the startScan()
method
Next, we will begin scanning for BLE devices. Make sure your smartwatch or any BLE device is on and nearby. Create a startScan
function and add it to your onPress
button:
const BleManagerModule = NativeModules.BleManager; const BleManagerEmitter = new NativeEventEmitter(BleManagerModule); const App = () => { const [isScanning, setIsScanning] = useState(false); useEffect(() => { let stopListener = BleManagerEmitter.addListener( 'BleManagerStopScan', () => { setIsScanning(false); console.log('Scan is stopped'); }, ); }, []); const startScan = () => { if (!isScanning) { BleManager.scan([], 5, true) .then(() => { setIsScanning(true); }) .catch(error => { console.error(error); }); } }; return ( <SafeAreaView> ...... <TouchableOpacity activeOpacity={0.5} style={styles.buttonStyle} onPress={startScan}> <Text style={styles.buttonTextStyle}> {isScanning ? 'Scanning...' : 'Scan Bluetooth Devices'} </Text> </TouchableOpacity> </SafeAreaView> ) }
We used the NativeEventEmitter
to listen to react-native-ble-manager
events like stopping scan, disconnecting, and so on. We have a state to check if scanning has started or not.
Next, in our startScan
function, we are scanning for devices only if the isScanning
state is false. This is because when it is true, it simply means that our BleManager
is still scanning.
BleManager.scan
takes 3 parameters — serviceUUIDs
, seconds
, and allowDuplicates
.
serviceUUIDs
is an array. It includes the UUID of the service to look for. We are leaving it empty because we are not searching for any particular device in mind but for all nearby devices.
seconds
is the time limit we want our search to run for.
allowDuplicates
lets us allow duplicates to be found. It only works for IOS devices.
Using the getConnectedPeripherals()
method
getConnectedPeripherals
is a react-native-ble-manager
method that allows us to get already-connected devices.
After our scan, we want to get the list of these connected devices along with the newly discovered ones. To do that, we will use our NativeEventEmitter
event listener to listen when our BleManager has stopped, then invoke our getConnectedPeripherals
.
See the code below:
const peripherals = new Map() const [bluetoothDevices, setBluetoothDevices] = useState([]); useEffect(() => { let stopListener = BleManagerEmitter.addListener( 'BleManagerStopScan', () => { setIsScanning(false); console.log('Scan is stopped'); handleGetConnectedDevices(); }, ); }, []); const handleGetConnectedDevices = () => { BleManager.getConnectedPeripherals([]).then(results => { if (results.length == 0) { console.log('No connected bluetooth devices'); } else { for (let i = 0; i < results.length; i++) { let peripheral = results[i]; peripheral.connected = true; peripherals.set(peripheral.id, peripheral); setConnected(true); setBluetoothDevices(Array.from(peripherals.values())); } } }); };
We are creating an empty object using the new Map()
method and assigning it to a variable called peripherals
. The object remembers the original insertion order of the keys and allows us to set our scanned BLE devices.
After we have scanned and retrieved both connected devices, we will then map through the array of peripherals
and display them:
// render list of bluetooth devices const RenderItem = ({peripheral}) => { const color = peripheral.connected ? 'green' : '#fff'; return ( <> <Text style={{ fontSize: 20, marginLeft: 10, marginBottom: 5, color: isDarkMode ? Colors.white : Colors.black, }}> Nearby Devices: </Text> <TouchableOpacity onPress={() => connectToPeripheral(peripheral)}> <View style={{ backgroundColor: color, borderRadius: 5, paddingVertical: 5, marginHorizontal: 10, paddingHorizontal: 10, }}> <Text style={{ fontSize: 18, textTransform: 'capitalize', color: connected ? Colors.white : Colors.black, }}> {peripheral.name} </Text> <View style={{ backgroundColor: color, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }}> <Text style={{ fontSize: 14, color: connected ? Colors.white : Colors.black, }}> RSSI: {peripheral.rssi} </Text> <Text style={{ fontSize: 14, color: connected ? Colors.white : Colors.black, }}> ID: {peripheral.id} </Text> </View> </View> </TouchableOpacity> </> ); }; return ( <SafeAreaView> ... {/* list of scanned bluetooth devices */} {bluetoothDevices.map(device => ( <View key={device.id}> <RenderItem peripheral={device} /> </View> ))} </SafeAreaView> )
Below is an example of a newly discovered nearby BLE device, or peripheral
, being displayed:
Connecting to and disconnecting from a BLE device
When we click on the peripheral
, we want to connect to it. We also want to be able disconnect after we have connected to the peripheral
.
To connect and disconnect to the peripheral
, react-native-ble-manager
has two methods — connect()
and disconnect()
— that allow us to connect or disconnect with peripherals
via their id
:
const connectToPeripheral = peripheral => { if (peripheral.connected) { BleManager.disconnect(peripheral.id).then(() => { peripheral.connected = false; setConnected(false); alert(`Disconnected from ${peripheral.name}`); }); } else { BleManager.connect(peripheral.id) .then(() => { let peripheralResponse = peripherals.get(peripheral.id); if (peripheralResponse) { peripheralResponse.connected = true; peripherals.set(peripheral.id, peripheralResponse); setConnected(true); setBluetoothDevices(Array.from(peripherals.values())); } alert('Connected to ' + peripheral.name); }) .catch(error => console.log(error)); } };
In the code above, we are checking if the peripheral
is connected or not. If it is, then we want to disconnect from it and set the peripheral.connected
key to false
. If it is not, we want to connect to the peripheral
and set the peripheral.connected
value to true
.
We also added a pop-up alert that will inform us when we have connected to or disconnected from a device:
Reviewing our full React Native BLE application code
You have now learned how to integrate BLE in a React Native application using react-native-ble-manager
. Our full project code can be found on GitHub and is also available in the code block below:
import React, {useState, useEffect} from 'react'; import { Text, View, Platform, StatusBar, ScrollView, StyleSheet, Dimensions, SafeAreaView, NativeModules, useColorScheme, TouchableOpacity, NativeEventEmitter, PermissionsAndroid, } from 'react-native'; import BleManager from 'react-native-ble-manager'; import {Colors} from 'react-native/Libraries/NewAppScreen'; const BleManagerModule = NativeModules.BleManager; const BleManagerEmitter = new NativeEventEmitter(BleManagerModule); const App = () => { const peripherals = new Map(); const [isScanning, setIsScanning] = useState(false); const [connected, setConnected] = useState(false); const [bluetoothDevices, setBluetoothDevices] = useState([]); useEffect(() => { // turn on bluetooth if it is not on BleManager.enableBluetooth().then(() => { console.log('Bluetooth is turned on!'); }); // start bluetooth manager BleManager.start({showAlert: false}).then(() => { console.log('BLE Manager initialized'); }); let stopListener = BleManagerEmitter.addListener( 'BleManagerStopScan', () => { setIsScanning(false); console.log('Scan is stopped'); handleGetConnectedDevices(); }, ); if (Platform.OS === 'android' && Platform.Version >= 23) { PermissionsAndroid.check( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ).then(result => { if (result) { console.log('Permission is OK'); } else { PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ).then(result => { if (result) { console.log('User accept'); } else { console.log('User refuse'); } }); } }); } return () => { stopListener.remove(); }; }, []); const startScan = () => { if (!isScanning) { BleManager.scan([], 5, true) .then(() => { setIsScanning(true); }) .catch(error => { console.error(error); }); } }; const handleGetConnectedDevices = () => { BleManager.getConnectedPeripherals([]).then(results => { if (results.length == 0) { console.log('No connected bluetooth devices'); } else { for (let i = 0; i < results.length; i++) { let peripheral = results[i]; peripheral.connected = true; peripherals.set(peripheral.id, peripheral); setConnected(true); setBluetoothDevices(Array.from(peripherals.values())); } } }); }; const connectToPeripheral = peripheral => { if (peripheral.connected) { BleManager.disconnect(peripheral.id).then(() => { peripheral.connected = false; setConnected(false); alert(`Disconnected from ${peripheral.name}`); }); } else { BleManager.connect(peripheral.id) .then(() => { let peripheralResponse = peripherals.get(peripheral.id); if (peripheralResponse) { peripheralResponse.connected = true; peripherals.set(peripheral.id, peripheralResponse); setConnected(true); setBluetoothDevices(Array.from(peripherals.values())); } alert('Connected to ' + peripheral.name); }) .catch(error => console.log(error)); /* Read current RSSI value */ setTimeout(() => { BleManager.retrieveServices(peripheral.id).then(peripheralData => { console.log('Peripheral services:', peripheralData); }); }, 900); } }; const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; // render list of bluetooth devices const RenderItem = ({peripheral}) => { const color = peripheral.connected ? 'green' : '#fff'; return ( <> <Text style={{ fontSize: 20, marginLeft: 10, marginBottom: 5, color: isDarkMode ? Colors.white : Colors.black, }}> Nearby Devices: </Text> <TouchableOpacity onPress={() => connectToPeripheral(peripheral)}> <View style={{ backgroundColor: color, borderRadius: 5, paddingVertical: 5, marginHorizontal: 10, paddingHorizontal: 10, }}> <Text style={{ fontSize: 18, textTransform: 'capitalize', color: connected ? Colors.white : Colors.black, }}> {peripheral.name} </Text> <View style={{ backgroundColor: color, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }}> <Text style={{ fontSize: 14, color: connected ? Colors.white : Colors.black, }}> RSSI: {peripheral.rssi} </Text> <Text style={{ fontSize: 14, color: connected ? Colors.white : Colors.black, }}> ID: {peripheral.id} </Text> </View> </View> </TouchableOpacity> </> ); }; return ( <SafeAreaView style={[backgroundStyle, styles.mainBody]}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <ScrollView style={backgroundStyle} contentContainerStyle={styles.mainBody} contentInsetAdjustmentBehavior="automatic"> <View style={{ backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, marginBottom: 40, }}> <View> <Text style={{ fontSize: 30, textAlign: 'center', color: isDarkMode ? Colors.white : Colors.black, }}> React Native BLE Manager Tutorial </Text> </View> <TouchableOpacity activeOpacity={0.5} style={styles.buttonStyle} onPress={startScan}> <Text style={styles.buttonTextStyle}> {isScanning ? 'Scanning...' : 'Scan Bluetooth Devices'} </Text> </TouchableOpacity> </View> {/* list of scanned bluetooth devices */} {bluetoothDevices.map(device => ( <View key={device.id}> <RenderItem peripheral={device} /> </View> ))} </ScrollView> </SafeAreaView> ); }; const windowHeight = Dimensions.get('window').height; const styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', height: windowHeight, }, buttonStyle: { backgroundColor: '#307ecc', borderWidth: 0, color: '#FFFFFF', borderColor: '#307ecc', height: 40, alignItems: 'center', borderRadius: 30, marginLeft: 35, marginRight: 35, marginTop: 15, }, buttonTextStyle: { color: '#FFFFFF', paddingVertical: 10, fontSize: 16, }, }); export default App;
Conclusion
In this tutorial, we learned how to enable and scan for Bluetooth Low Energy devices within our React Native application. We also saw how to connect to the devices after we have discovered them.
react-native-ble-manager
has other methods you can explore. For example, isPeripheralConnected()
allows us to check if our device is connected, while createBond()
allows us to pair with the device.
If you followed the tutorial, you shouldn’t find it difficult to use other methods. Be sure to comment below if you have any questions or run into a problem.
LogRocket: Instantly recreate issues in your React Native apps.

LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
I get the error:
null is not an object (evaluating ‘bleManager.scan’)
when I tap on “Scan Bluetooth Devices” button.
I’m running it in Expo Go in an Android simulator. Is it supposed to be run in Expo Go, either in a simulator or real device?
Hey Alan,
The issue is with Expo. Expo does not support the ble-manager package. You need to use a native project for it
When I press scan bluetooth devices, and it said, need android.permission.BLUETOOTH_SCAN permission for AttributionSource {uid= 10162, packageName = com.yourprojectname, attributionTag= null, token = [email protected], next= null}: GattServiceregisterScanner
using react native cli,getting .BLUETOOTH_SCAN permission error on android 12 ad for android 10, 11 no any bluetooth device found
I am on android 13. I click the scan button and nothing returns. I know there are ble devices by using nRF Connect. Any idea why no devices would display after scanning?