We’ve been working on a project with Zagster that uses Bluetooth Low Energy (BLE) to connect to a BLE-powered bike lock so that we can authorize, read, write, and receive notifications from the lock, resulting in a better, faster, and easier riding experience.
Round 1: iOS and react-native-ble
For our first attempt we decided to use react-native-ble
to connect and
interact with the locks. We were excited to use this library largely because it
would allow us to use the same application code for iOS and Android. After a lot
of hard work and a few PR’s we finally got this library talking to the locks,
connecting and generally doing what it is supposed to do.
Round 1.5: Android and react-native-ble
After shipping iOS it was time to start focusing on Android. Everything seemed
fine except BLE wouldn’t work consistently. We tried changing our JavaScript
code that interacted with the locks, we tried modifying the native code
in react-native-ble
, we even tried changing phones and locks in case they were
problematic. Nothing we changed seemed to get BLE to work consistently on
Android.
We knew that there were issues with BLE reliability on many Android phones, but the behavior we were seeing was so erratic that it became clear that our BLE stack needed an overhaul.
Round 2: Android, SweetBlue, and react-native-ble
At this point we assumed the issue was the native Java code in
react-native-ble
so we decided we’d replace it. Instead of re-writing using
the Android included APIs for BLE we decided to use SweetBlue. SweetBlue
softens a lot of the sharp edges of the native APIs and provides a much better
interface in comparison to the native APIs.
We eventually got the Java portion of react-native-ble
replaced with SweetBlue
and things seemed more stable. Unfortunately we still had issues under
non-perfect conditions and real world testing. Things seemed better but we still
weren’t able to ship with these issues.
A 7 Layer BLE(ito)
Debugging the issues we were seeing was overwhelming due to the several layers of external libraries.
From lowest level to highest level, we had:
- The Java code within
react-native-ble
that uses SweetBlue to interface with the Android BLE APIs react-native-ble
bindings, which forwards events from the Java layer to the Noble library- Noble, which provides a generic JavaScript API for interacting with BLE
- Axa.js, a custom library that provides an API to interact with AXA locks through Noble, such as authorizing, locking/unlocking, reading the lock status, etc.
- Several libraries that use Axa.js to keep track of locks, their status, and other metrics/uses.
Some of these layers were in our app, and some were scattered around GitHub as their own repositories. Adding logging was not trivial, so the feedback loop was slow and unreliable. Debugging was also painful. Finding the source of the bugs and tracking them through the many layers was difficult and not sustainable.
Round 3: Android and SweetBlue
After trying to massage SweetBlue into react-native-ble
and experiencing more
issues we only had two options left: inspect/debug each layer extensively, or
remove react-native-ble
in favor of a custom native module. We opted for the
custom native module and moved all BLE code into the app so we could solve
problems with more agility.
We took the largely generic SweetBlue code we wrote for react-native-ble
and
developed a native API specific to working with our BLE locks. This allowed us
to put all the complex BLE interactions directly in the Java code removing a lot
of our dependency on JavaScript. This allowed us to reduce the amount of events
flowing between JS and Java, which increased reliability and performance.
After migrating to our custom Java code, connecting and reconnecting to locks
became significantly more stable. We no longer saw the reconnection problems,
failed locking, and other issues that we experienced when using our
react-native-ble
based stack.
Round 4: iOS and RZBluetooth
After shipping the Android app, we were left with a somewhat disjointed BLE
stack, since the iOS app was still using react-native-ble
, while Android was
using our new custom native module. This meant that we had to maintain two very
different paths for handling BLE, which was hard to maintain and reason about.
To consolidate our codebase and to reduce the complexity of BLE on iOS, we decided to take a similar approach for iOS as we did with Android.
After surveying the BLE library options for iOS, we came across RZBluetooth, which is a simple block-based wrapper around iOSs CoreBluetooth. This provides a more convenient interface for asynchronous operations than working with delegates and keeps our code consistent with the constructs used in our Java and JavaScript code.
Migrating was relatively simple. We used the existing Android-specific JavaScript code to guide the API of the new Objective-C native module. The end result allowed us to completely consolidate the platform-specific JavaScript code.
LOC (Lines of Code) Impact
Here’s a LOC comparison between the approaches we took:
BLE Stack | LOC |
---|---|
react-native-ble Java |
802 |
react-native-ble SweetBlue Java |
465 |
react-native-ble Objective-C |
650 |
Custom SweetBlue Java | 489 |
Custom Objective-C | 365 |
We removed a lot of code by removing react-native-ble
and switching to custom
native modules. We were able to remove several dependencies which aren’t counted
in the LOC analysis above.
In addition to slimming down the amount of Java and Objective-C code we needed
to maintain we were also able to remove an entire 759 LOC JavaScript library
that we wrote to talk between JavaScript and react-native-ble
. We did have to
write more JavaScript glue code but it was significantly smaller than the axa.js
library we wrote clocking in at only 240 LOC.
Conclusion
Without going through the pain we experienced, we wouldn’t have ended up with as great a solution. It’s easy to look back in hind-sight and think that we started off on the wrong path, but the knowledge we picked up along the way was essential to the success of the custom BLE modules.
That being said, if either of us started a new React Native project that depends on BLE tomorrow, we’d start with custom native modules. We’re extremely happy with the native modules we wrote and both seem to have improved performance and stability. Most React Native solutions won’t require writing native code, but it’s a valuable skill to have when you need more control over low-level behavior.