Writing an app that communicates with a Bluetooth device can be very challenging. These days, apps tend to do a lot of work behind the scenes, such as syncing data, updating firmware, etc., without the user knowing about it. When something goes wrong with a process that is not directly visible, it can be quite difficult to detect it and figure out what is causing it.
I have worked on a handful of such apps now, and along the way, I have picked up a few best practices that I believe make the process go much more smoothly.
1. Know the Bluetooth Library and Use It
The most important thing you can do when building a Bluetooth application is to read the documentation for your intended platform’s Bluetooth library. Whether you are coding for Android or iOS, you will find nuances in the Bluetooth implementation that cause behaviors you wouldn’t necessarily expect.
For example, in iOS, you can make many characteristic writes back-to-back without waiting for one to complete, and eventually all of the writes will complete. In contrast, if you try the same thing in Android, you will find that writes will occasionally be dropped. If you do the work of reading through the docs up front, you will save yourself a lot of debugging headaches later on.
I also highly recommend not using a third-party Bluetooth library. The Bluetooth libraries provided by iOS and Android do not require a ton of boilerplate code. Adding a third-party wrapper on top of them may save you a little time when you are first getting started, but it will likely make it harder for you to debug issues in the future. In my opinion, it’s not worth it.
2. Add a Feature Sandbox
A feature sandbox is a place within your app where you can quickly spike out implementations of new features without worrying about taking the time to implement a pretty user interface. I prefer to do this by creating a “Developer” screen where you can plop down buttons and display device raw device state. This screen is only available to engineers, developers, and testers.
Oftentimes, the device your app is communicating with is being developed at the same time as your app. The developers of the device will likely need some way of quickly testing new features that they are working on. Generic Bluetooth apps like LightBlue Explorer can be useful for testing simple things, but eventually, testers will need something more. If you can quickly build a feature that allows them to test more effectively, you will probably end up saving yourself a lot of time in the long run.
3. Isolated Logging for Bluetooth Communication
Good logging practices are important in any app, but they’re crucial in an app where Bluetooth is used. Connection and communication problems are going to happen. As a developer, it is going to be your responsibility to figure out why. Was there a problem with your app? Did the device go out of range or drop the connection unexpectedly? Sometimes there isn’t actually a technical problem with the app, but rather a misunderstanding of how the system should work. The only way you can identify the problem for sure is to create a log that clearly indicates what events happened, and when.
There are a lot of ways you can handle logging. Some people use different levels of verbosity. Others create completely separate logs based on unique areas of functionality. The technique you use is up to you, but I highly recommend that you come up with something that provides the ability to view only Bluetooth activity and to easily export the logs from the phone.
The idea is to set up one log file that just contains connection state changes, characteristic reads/writes, notifications, etc., along with the timestamps when those things occurred. This will make it much easier to see if Bluetooth communication is happening as you expect without all the noise of everything else that is going on in the app. Also, it won’t always be you who notices the problem, so providing a way for someone who discovers an issue to immediately export the log and send it to you will be a huge win.
4. Build a Simulated Bluetooth Device
Testing apps in the simulator is really convenient until your app includes Bluetooth functionality. The iOS and Android simulators don’t offer any support for simulating Bluetooth communication. However, that doesn’t mean that you can’t build it in yourself.
In one app I worked on, we built a simulated Bluetooth device that was compiled in to debug builds only. The simulated device hooked into the application at the lowest possible level by calling the same Bluetooth library delegate methods that the OS does. This allowed us to exercise a maximum amount of application code, including that which parses the data written to the Bluetooth characteristics.
Our simulated device included a lightweight HTTP server to expose routes which allowed us to update the state on the simulated device at runtime. This way, we could run the app in the simulator. Then we used a script to send requests to the HTTP server, which resulted in changes to the internal simulated device. We could then see that change reflected in the UI.
This probably sounds like a lot of work, but once we had the infrastructure in place, it was not difficult to maintain and update it when new features were added to the real device. The value we got from being able to fully test our app in the simulator made it well worth it.
5. Carefully Define Your Service
Finally, if you have any input into the process of defining the Bluetooth service that the device will provide, put a lot of thought into how the structure of the service will affect the app. Here are a few things to keep in mind:
Handling changes to the device
The device will very likely change throughout the development phase. If there are no constraints in place, characteristics will be added and removed over time, and the meaning of characteristic data will probably change as well. How are you going to handle those changes? You should discuss this with all parties involved and decide on some rules upfront for what can be changed and what can’t.
In the app, I highly recommend reading the firmware version of the device before reading any other data. Then enforce a rule that the mechanism for obtaining the firmware version (advertising data or characteristic info) can never change. You can then use the firmware version as a key for interpreting the rest of the characteristic data.
iOS device characteristics
In iOS, there is no way to discover a subset of device characteristics. When you connect to a device, if you want to know what characteristics it supports, you have to discover all of them. If your device is going to have a lot of features and you make each feature have its own characteristic, you will be increasing the amount of time it takes for the app to fully connect and discover a device. If quick connection times are a high priority for you, this could be problematic. You may want to consider having fewer characteristics that are more generic.
Bluetooth characteristic notifications
Finally, take advantage of Bluetooth characteristic notifications. A notification is like an alert that can be sent from the device. Rather than having your application continually poll the device for state changes, allow the device to notify on a characteristic when something changes. Then the app can go and read one or more characteristics to retrieve the new data. One major benefit of this approach is that is allows for instantaneous feedback in the app when the state of the device changes.