Search code examples
androidbluetooth-lowenergyrx-javareactive-programmingrxandroidble

Using multiple .subscribe() statements with RxJava


My Android app for a BLE peripheral writes to 2 different device characteristics and receives notifications from 2 others. The developers of the RxAndroidBle library caution against multiple subscriptions on the same RxBleConnection instance, but I don't see any realistic way of combining all these I/O operations into a single .subscribe(), especially since one of the notifications is a pretty constant data "firehose".

Not knowing any better, I have been simply storing the RxBleConnection in a variable and using it in multiple .subscribe()s. As far as I can tell, this has been working OK. I've investigated the RxAndroidBle library's ConnectionSharingAdapter but, though I have analyzed the code, I don't understand what benefits it offers over my simplistic approach (though I would love to know).

In general, some elaboration on how multiple .subscribe()s introduce state, and the potential pitfalls, would be helpful. Questions:

  1. What is wrong with storing RxBleConnection in a variable and using it for multiple .subscribe()s?
  2. If this is a problem, how does ConnectionSharingAdapter solve it?
  3. What does it mean to say that multiple subscriptions "introduce state", and how can this cause problems?
  4. Is there any clean way of combining all four characteristic I/O operations into a single .subscribe() (that won't degrade performance)?

Solution

  • What is wrong with storing RxBleConnection in a variable and using it for multiple .subscribe()s? (...) What does it mean to say that multiple subscriptions "introduce state", and how can this cause problems?

    These two questions are almost the same. An RxBleConnection is an abstraction over a state in which a BLE client exchanges some handshake packets with a BLE server after which both the client and the server are considered connected. Unfortunately due to various reasons this connection can be broken at almost any time (and that happens quite often) which a single stored variable of RxBleConnection cannot easily express in a reactive manner.

    On the other hand observing RxBleDevice.establishConnection() will propagate an error to the subscriber once the connection will get broken. Although none of the functionality that the emitted RxBleConnection will work once the connection is broken - a saved variable of the connection will not inform about the problem when it happens.

    So if a user saves an RxBleConnection to a variable - it introduces a state in which the variable may be in and the user is responsible for propagating (clearing the variable) errors that may happen later. As an opposite subscribing to .establishConnection() will emit an exception when the connection cannot be used.

    As many of programmers may have noticed during their practice - managing state is the most common source of bugs in the applications. Reducing state is a way to mitigate the risk of bugs.

    There is an excellent (but quite advanced) talk from Devoxx by Jake Wharton: Managing State with RxJava by Jake Wharton

    If this is a problem, how does ConnectionSharingAdapter solve it?

    The .establishConnection() observable does not allow to have more than one simultaneous subsription because of the stateful nature of BLE connection and communication (request-response is a well known pattern) and having more than one .subscribe() that uses the same connection may cause interference with the other without a clear trace in the code. That is why BleAlreadyConnectedException was introduced for users that do not follow all places in the code that use an RxBleConnection. ConnectionSharingAdapter was introduced as a helper for users that conciously decide to share a single connection between more than one interactor. If the RxBleConnection gets broken the ConnectionSharingAdapter will propagate the error to all Subscribers.

    Is there any clean way of combining all four characteristic I/O operations into a single .subscribe() (that won't degrade performance)?

    In most situations it is possible to cleanly combine many I/Os. The above mentioned talk touches this topic. Proper combining multiple I/Os comes with a cost of additional allocations but it is rarely a problem when dealing with BLE because of the low speed of this communication channel.