Bluetooth Low Energy Client on a React Native Application

Code walkthrough for a React Native application running as a Bluetooth Low Energy (BLE) client of the ESP32 BLE server.

June 08, 2020

Make sure the mobile device you are going to use has BLE as a feature and do not forget to always turn on Bluetooth. Because I did.

What is covered

Code repository


Installing react-native-ble-manager

As you may see on the readme file of the fantastic react-native-ble-manager package created by, project led by Marco Sinigaglia (marcosinigaglia github account), you have to add some user-permission keys to AndroidManifest.xml file and to iOS Info.plist the NSBluetoothAlwaysUsageDescription key.

Adding listeners for specific BLE events

Please open /src/App.js file to see how the react native NativeModule called BleManager (line 30) is leveraged. Once we instantiate that module under BleManagerModule, we also need an instance of the NativeEventEmitter to be able to subscribe from JavaScript code to native events.

Under componentDidMount lifecycle method we are adding listeners for:

  • AppState change with this.handleAppStateChange to be able to detect when the app is becoming active from background (line 43)
  • BleManagerDiscoverPeripheral native event with this.handleDiscoverPeripheral (lines 52-55)
  • BleManagerStopScan native event with this.handleStopScan (lines 57-60)
  • BleManagerDisconnectPeripheral native event with this.handleDisconnectedPeripheral (lines 62-65)
  • BleManagerDidUpdateValueForCharacteristic native event with this.handleUpdateValueForCharacteristic (lines 67-70)

Beside setting up those event listeners we call this.startScan method on line 72.

We need to clean up things under componentWillUnmount lifecycle method to prevent memory leaks, so we remove all listeners from above on lines (92-98).

So, we now need to implement 5 methods to handle those 5 events we subscribed to. But we also need 3 more helper functions: startScan (lines 153-164) for discovering BLE peripherals around our mobile device like ESP32 server, writeNewSettings (lines 177-212) to send data to the the ESP32 server, hookUpSensorNotifications (lines 214-257) to be able to receive BLE notifications for the data streams from the 3 Arduino sensors.

handleAppStateChange (lines 75-90) is making sure that each time our app is becoming active from background looks for the connected peripherals.

handleStopScan (lines 129-151) is calling again the startScan method if there are no peripherals under that particular state key. It is also taking care of connecting to the ESP32 detected BLE server if that key under App component shows otherwise by calling this.hookUpSensorNotifications passing the detected device argument.

Please note that each time we scan for BLE devices around, we look for a particular device UUID, in other words the UUID of the ESP32 board.

handleDiscoverPeripheral (lines166-175) is storing each detected peripheral under the peripherals state key which is a JavaScript Map object to collect all these BLE enabled devices.

handleDisconnectedPeripheral (lines 100-110) looks to state for the particular peripheral object received from the native module and if it finds it, it sets its connected key to false.

Reading and writing BLE data

handleUpdateValueForCharacteristic (lines 112-127) is receiving the data passed by the native module as a stream of bytes (the usual way of formatting data not only for BLE communication but also for I2C), it decodes it using the decode function coming from /src/utils/utf8Convertor.js. The result is a string of x divided values that need to be split and parsed as integers before storing them into the component state.

writeNewSettings (lines 177-212) is responsible for receiving the new settingsArray from a child component of the App component. If what it is getting is something new, it retrieveServices first for the ESP32 device to make sure the connection is still alive, then writes those values after it changes them into a joined string then into a stream of bytes.

The render method is looking first for the state value under the isConnected key to conditionally render a preloader or the main Navigation component by passing to it sensorData and the writeNewSettings as props.