Search code examples
androidandroid-blerxandroidble

RxAndroidBle - How to disconnect all connected devices?


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!


Solution

  • 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.