Chimezie Innocent I am Chimezie, a software developer based in Nigeria. I am highly skilled in HTML, CSS, and JS to build web-accessible and progressive apps. I'm also skilled with React for web, React Native for Android apps, and Tailwind CSS. I write technical articles, too.

Using react-native-ble-manager in a mobile application

10 min read 2935 109

Integrating Ble In A React Native App With The React Native Ble Manager Package

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?

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:

Sample React Native App For Ble Project With Dark Mode Color Theme Enabled Showing Dark Background, White Text, And Blue Button

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:

Sample App From Previous Image With Popup Prompt To Allow Bluetooth To Be Turned On

Also, we have to grant our application access to the phone’s location to enable us to perform our scan:

Sample App From Previous Image With Popup Prompt To Allow Access To Device Location

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:

Sample App From Previous Image With List Of One Nearby Ble Device Shown

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:

Sample App From Previous Image With Popup Alert That Ble Device Has Been Disconnected

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 — .

Chimezie Innocent I am Chimezie, a software developer based in Nigeria. I am highly skilled in HTML, CSS, and JS to build web-accessible and progressive apps. I'm also skilled with React for web, React Native for Android apps, and Tailwind CSS. I write technical articles, too.

5 Replies to “Using react-native-ble-manager in a mobile application”

  1. 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?

    1. Hey Alan,

      The issue is with Expo. Expo does not support the ble-manager package. You need to use a native project for it

  2. 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

  3. using react native cli,getting .BLUETOOTH_SCAN permission error on android 12 ad for android 10, 11 no any bluetooth device found

  4. 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?

Leave a Reply