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.
For our first attempt we decided to use
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.
After shipping iOS it was time to start focusing on Android. Everything seemed
code that interacted with the locks, we tried modifying the native code
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
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.
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.
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-blethat uses SweetBlue to interface with the Android BLE APIs
react-native-blebindings, which forwards events from the Java layer to the Noble library
- 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.
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
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
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
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.
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.
Here’s a LOC comparison between the approaches we took:
|Custom SweetBlue Java||489|
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
react-native-ble. We did have to
library we wrote clocking in at only 240 LOC.
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.