I am using awesome rxandroidble library for BLE control.
I keep connection between activities.
Before I start scanning, I want to disconnect all connected devices first.
Sometimes It is not working if there are many connections.
This is the solution I am using:
public void doScan() {
if (isScanning()) return;
// disconnect all connected devices first:
while(BleController.getDefault().getDisconnectTriggerSubject().hasObservers()){
BleController.getDefault().getDisconnectTriggerSubject().onNext(null);
}
scanSubscription = rxBleClient.scanBleDevices(
new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build(),
new ScanFilter.Builder()
// add custom filters if needed
.build()
)
.filter(rxBleScanResult -> !TextUtils.isEmpty(rxBleScanResult.getBleDevice().getName()))
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(this::clearSubscription)
.subscribe(resultsAdapter::addScanResult, this::onScanFailure);
updateButtonUIState();
}
BleController
is initialized with the main application's context and keeps the connectionObservable
, disconnectTriggerSubject
, rxBleClient
.
What can be the better solution? Any help would be appreciated!
From your post I can see that you are mixing the BLE scanning/connection logic with the UI/Activity logic. This may be a problem to manage connections correctly.
What you could do is to put all the BLE logic to your BleController
which already has a good name but it seems that in your situation is rather a BleObjectsContainer
.
For instance you could only expose from the BleController
only observables that are fulfilling your specific use-cases in a way that the Activities
do not need to handle. i.e. Your BleController
could handle scanning:
private final BehaviorRelay<Boolean> isScanningPublishRelay = BehaviorRelay.create(false); // a relay (that cannot emit an error) that emits when a scan is ongoing
private Observable<ScanResult> scanDevicesWithNonNullNameObs = rxBleClient.scanBleDevices(new ScanSettings.Builder().build())
.filter(scanResult -> !TextUtils.isEmpty(scanResult.getBleDevice().getName()))
.doOnSubscribe(() -> isScanningPublishRelay.call(true)) // when scan is subscribed/started emit true
.doOnUnsubscribe(() -> isScanningPublishRelay.call(false)) // when scan is stopped emit false
.subscribeOn(AndroidSchedulers.mainThread()) // the above emissions will happen on the same thread. should be serialized
.unsubscribeOn(AndroidSchedulers.mainThread()) // the above emissions will happen on the same thread. should be serialized
.share(); // share this observable so no matter how many places in the code will subscribe the scan is started once and `isScanningPublishRelay` is called once
public Observable<ScanResult> scanDevicesWithNonNullName() { // getter for the scan observable
return scanDevicesWithNonNullNameObs;
}
And besides of scanning it would also handle your specific use-cases for each Activity
that needs it:
class ScanInProgress extends Throwable {
// ...
}
public Observable<YourActivityUseCaseModel> doYourSpecificStuff(Observable<RxBleConnection> connectionObservable) {
return Observable.merge(
connectionObservable,
isScanningPublishRelay
.takeFirst(aBoolean -> aBoolean)
.flatMap(ignored -> Observable.error(new ScanInProgress())) // this will only emit an error when a scan is ongoing
)
.flatMap(...); // do the rest of your stuff
}
This way in your activities you would only need to subscribe to whatever model they need and handle the BLE in a single place that is dedicated for it (BleController
).
In the above example you need to provide the Observable<RxBleConnection>
but it can be achieved in many different ways and could managed in BleController
as well so it would not be exposed in the interface.