Share

engineering

6min read

RxAndroidBLE - your most powerful tool for Bluetooth Low Energy coding!

RxAndroidBLE - your most powerful tool for Bluetooth Low Energy coding!

Disclaimer: this article was updated to match the RxJava 2 version of the library as the first one is no longer supported.

Some time ago we published here, a tutorial on how to work with Bluetooth Low Energy in Android. As you might have noticed, the whole process required many (many!) asynchronous callbacks, handling different numeric statuses, threading knowledge and more. Because at Polidea, our customer’s products become more and more intelligent and connected it was natural for us to simplify all this code duplication and complexity. ​ So we did it, and here it is! Let us introduce you to our new powerful toy, the RxAndroidBLE library. RxAndroidBle is the painkiller for Android’s Bluetooth Low Energy headaches. It is backed with RxJava 2, it basically implements complicated APIs as handy reactive observables. Long story short, the library can: ​

  • Fancy asynchronous operations support (read, write, notifications)
  • Thread management in order to meet Android contracts
  • Connection and operation error handling, etc.

In the following post, we will give you tips to get started with RxAndroidBLE library. ​ ​

Including into the project

The library is available for your convenience as Maven dependency on Central. Depending on your build system choice you use either: ​

Maven

<dependency>
  <groupId>com.polidea.rxandroidble2</groupId>
  <artifactId>rxandroidble</artifactId>
  <version>1.10.1</version>
  <type>aar</type>
</dependency>

or

Gradle

compile "com.polidea.rxandroidble2:rxandroidble:1.10.1"

Obtaining the client

Your entry point to the library is the RxBleClient class. For connection and operation handling we require that single instance of the client is used per app. It’s a good idea to use some dependency injection like Dagger and it’s scoped components but it’s up to you. Simple singletons and Application based containers are alright to use as well. The client itself can be obtained by simply call to: ​

​RxBleClient rxBleClient = RxBleClient.create(context);

Note: due to how RxJava 2 has changed error handling an additional error handler code is needed inside the application that uses RxAndroidBle library.

Finding a device

Device discovery is very simple and handy. Scan lifecycle management is linked to the Observable lifecycle. When you subscribe to the Observable the scan will be initiated and when you dispose it will end. ​There are various parameters you can set on the scan like scan duty cycle or advertisement filtering.

Disposable scanDisposable = rxBleClient.scanBleDevices(
    ScanSettings.Builder()
        .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
        .build(),
    ScanFilter.Builder()
        .setServiceUuid(yourParcelUuid)
        .build() // Optional, more than one ScanFilter may be passed as varargs.
)
    .subscribe(scanResult -> {
        // Process scan result here.
    });

// When done, just unsubscribe.
scanDisposable.dispose()

When more than one ScanFilter is being passed the result will be emitted if any of the filters will match. If no filters are used then all scanned devices will be emitted unless the app is in the background.

Note: ScanSettings and ScanFilter classes should be from com.polidea.rxandroidble2 package, NOT from android.bluetooth.le despite being almost the same functionality they have some slight differences. It is always good to inspect what they allow you to do!

Note 2: This post for brevity omits error handling in every subscribe statement. Bear in mind that BLE connection may always fail for reasons that are out of control and therefore a production code should be prepared!

Connecting to the device

In order to connect you will need the RxBleDevice instance. It can be taken from the scan result or created manually with a mac address of the Bluetooth device. All you need to do is to call establishConnection method and get the RxBleConnection, which is a handle, used to process BLE operations with a connected device. ​

String macAddress = "AA:BB:CC:DD:EE:FF";
RxBleDevice rxBleDevice = rxBleClient.getBleDevice(macAddress);

Disposable disposable = rxBleDevice.establishConnection(false) // <-- autoConnect flag
    .subscribe(rxBleConnection -> {
        // All GATT operations are done through the rxBleConnection.
    });

// When done... dispose and forget about connection teardown :)
disposable.dispose();

By default there can be only one connection call at time. This decision was made to protect users from unintentionally mingling with their own communication that could have been started with a different part of the code. Often the peripherals are implemented as stateful which may lead to trivial but hard to debug issues. This constraint may be easily removed by using ReplayingShare. ​

Discovering services

Although it is not required to do it manually in order to read/write, you can always discover supported services with the following call. ​

rxBleDevice.establishConnection(false)
    .flatMap(RxBleConnection::discoverServices)
    .subscribe(RxBleDeviceServices discoveryResult -> {
        // Process service discovery result on your own.
    });

Reading / writing to the device

Almost no BLE device is useful if it cannot be interacted. Once you are connected you can operate as presented below: ​

Read

device.establishConnection(false)
    flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUUID))
    .subscribe(characteristicValue -> {
        // Read characteristic value.
    });

Write

device.establishConnection(false)
    .flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(characteristicUUID, bytesToWrite))
    .subscribe(characteristicValue -> {
        // Characteristic value confirmed.
    });

Multiple reads

device.establishConnection(false)
    .flatMap(rxBleConnection -> Observable.combineLatest(
        rxBleConnection.readCharacteristic(firstUUID),
        rxBleConnection.readCharacteristic(secondUUID),
        YourModelCombiningTwoValues::new
    ))
    .subscribe(model -> {
        // Process your model.
    });

Read and write combined

device.establishConnection(false)
    .flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUuid)
        .doOnNext(bytes -> {
            // Process read data.
        })
        .flatMap(readBytes -> rxBleConnection.writeCharacteristic(characteristicUuid, bytesToWrite)) // bytesToWrite may be customised according to readBytes.
    .subscribe(writeBytes -> {
        // Written data.
    });

You can also listen to change notifications

device.establishConnection(false)
    .flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
    .doOnNext(notificationObservable -> {
        // Notification has been set up
    })
    .flatMap(notificationObservable -> notificationObservable) // <-- Notification has been set up, now observe value changes.
    .subscribe(bytes -> {
        // Given characteristic has been changes, here is the value.
    });

There are also overloads of setupNotification function for some project specific edge cases. Most of the time the easiest version works just fine.

Closing connection

Do not worry about a complex connection teardown. Disconnect, closing the GATT is managed for you. The only thing you need to do is to disposable.dispose(). As simple as that! ​

Conclusion

IoT is no longer a future but it is all over the place and Bluetooth programming became our reality. Fortunately thanks to reactive concepts it does not have to be a headache anymore. We believe that open source is a part of our mission so here we are, sharing our battlefield tested knowledge to you. Do not forget to check the repository on Github as new features are coming!

Share

Paweł

Senior Software Engineer

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!