I am currently implementing a protocol for a Bluetooth device and i am using the RxAndroidBle Library (version 1.4.3).
I have to request data from the device by writing to characteristic and then listening to the response via a characteristic notification.
To combine the 2 operations (writing and listening) I am using the code from: https://stackoverflow.com/a/41140523/734385
connectionObservable
.flatMap( // when the connection is available...
rxBleConnection -> rxBleConnection.setupNotification(AP_SCAN_DATA), // ... setup the notification...
(rxBleConnection, apScanDataNotificationObservable) -> Observable.combineLatest( // ... when the notification is setup...
rxBleConnection.writeCharacteristic(AP_SCAN_DATA, writeValue), // ... write the characteristic...
apScanDataNotificationObservable.first(), // ... and observe for the first notification on the AP_SCAN_DATA
(writtenBytes, responseBytes) -> responseBytes // ... when both will appear return just the response bytes...
)
)
.flatMap(observable -> observable)
This approach works for me, the only problem is that the code gives me only the first 20 bytes (due to the apScanDataNotificationObservable.first()).
Unfortunately, I don't know the size of the package I am receiving. I can only extract the information from the header of the first 20 bytes. It seems like the RxJava buffer function all require to know the size beforehand.
Is there a way to make this work cleanly with the code above as part of the Rx chain?
In other words, can I control the number of emission based on the very first emission of an Rx chain?
Or do I have a completely wrong approach?
It is possible to achieve what you want.
The easiest way would be to exchange the Observable.combineLatest(...) to:
Observable.merge(
rxBleConnection.writeCharacteristic(AP_SCAN_DATA, writeValue).ignoreElements(), // send the request but ignore the returned value
apScanDataNotificationObservable.takeUntil(newResponseEndWatcher()) // take the response notifications until the response end watcher says so
);
Where newResponseEndWatcher() would need to contain the logic for determining if the received values are all that is expected. It could look like this:
private Func1<byte[], Boolean> newResponseEndWatcher() {
return new Func1<byte[], Boolean>() {
private static final int NOT_INITIALIZED = -1;
private int totalLength = NOT_INITIALIZED;
private int receivedLength = NOT_INITIALIZED;
#Override
public Boolean call(byte[] bytes) {
if (isNotInitialized(totalLength)) { // if it is the first received value
// parse totalLength from the header
}
// update receivedLength
return receivedLength >= totalLength;
}
private boolean isNotInitialized(int value) {
return value == NOT_INITIALIZED;
}
};
}
Just have in mind that Func1 which is the result newResponseEndWatcher() is stateful. If one would store into a variable the observable that is result of apScanDataNotificationObservable.takeUntil(newResponseEndWatcher()) the next subscriptions could end prematurely.
To mitigate this problem one may use Observable.using() function that would call newResponseEndWatcher() each time it is subscribed and then create a new apScanDataNotificationObservable.takeUntil(newResponseEndWatcher):
Observable.using(
() -> newResponseEndWatcher(), // create a new response end watcher on each subscription
responseEndWatcher -> apScanDataNotificationObservable.takeUntil(responseEndWatcher), // create the response observable that will complete properly
responseEndWatcher -> { /* ignored, responseEndWatcher will get GCed eventually */ }
);
Related
I am creating a RxJava2 chain where in I want to enable and disable notification. the flow I am setting is as follows.
establish a connection.
set the notification to READ_STATUS UUID.
if the returned byte is zero then perform a write byte 01 to WRITE_STATUS UUID and after WRITE_STATUS, enable the notification of READ_STATUS UUID to verify it has byte value 1.
else if the returned byte is 1 then just enable other indicators (UUID1, UUID2,UUD3) and read the value.
I have a problem at step 2 and 3 where I am reading the value of READ_STATUS UUID by enabling the notification. in order to re-read the value, I probably need to disable the notification and then again enable it. And to disable to the notification I have to dispose that particular setupNotification .
Code is as follows
connectDisposable=
device.establishConnection(false)
.flatMap(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
return Observable.just(rxBleConnection);
})
.flatMap(rxBleConnection ->mRxBleConnection.setupNotification(READ_STATUS,NotificationSetupMode.QUICK_SETUP).flatMap(it->it))
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return false;// dispose above to disable the notification
else
return true; // no need to disable the notification and continue writing
})
.flatMap(bytes -> {
return Observable.zip(
mRxBleConnection.writeCharacteristic(WRITE_STATUS, new byte[]{1}).toObservable(),
// setupNotification again to check whether read status has 1 or not
mRxBleConnection.setupNotification(READ_STATUS, NotificationSetupMode.QUICK_SETUP).flatMap(it->it),
Pair::new
);
})
.flatMap(bytes ->{
byte [] val= bytes.first;
if(getByteValue(val) == 1){
return Observable.zip(
mRxBleConnection.setupIndication(HISTORY, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 1 ")).flatMap(it -> it),
mRxBleConnection.setupIndication(PARAMCHECK, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 2 ")).flatMap(it -> it),
mRxBleConnection.setupIndication(FAULTINFO, NotificationSetupMode.QUICK_SETUP).doOnNext(observable -> Log.e(TAG,"Here 3 ")).flatMap(it -> it),
Data::Readings);
}
return Observable.empty();
}).subscribe(data -> {
});
The problem with this code is my takeUntil is firing at the last it does not dispose the previous setupNotificaion operation so that I can re read it later.
I tried solution mentioned over this thread but unfortunately I am not sharing the RxBleConnection
The problem with this code is my takeUntil is firing at the last it does not dispose the previous setupNotificaion operation so that I can re read it later.
The problem is that your condition is inverted. From .takeUntil() Javadoc:
* #return an Observable that first emits items emitted by the source Observable, checks the specified
* condition after each item, and then completes when the condition is satisfied.
You have used:
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return false;// dispose above to disable the notification
else
return true; // no need to disable the notification and continue writing
})
where it should be satisfied (return true) when the upstream should get disposed:
.takeUntil(bytes -> {
if(getByteValue(bytes)==0)
return true;// dispose above to disable the notification
else
return false; // no need to disable the notification and continue writing
})
To unsubscribe or to dispose setupNotification or setupIndication one can use the following code. I am sure there could be different ways but so far I could find this
private Observable<Pair<byte[],byte[]>> getValueFromIndication(RxBleConnection rxBleConnection){
final PublishSubject<Boolean> unsubscribeOperation= PublishSubject.create();
return Observable.zip(
rxBleConnection.setupIndication(TSDictionary.FAULT_RETRY_COUNT_SEQUENCE,NotificationSetupMode.QUICK_SETUP).flatMap(it->it).takeUntil(unsubscribeOperation),
rxBleConnection.setupIndication(TSDictionary.FAULT_RETRY_INFORMATION,NotificationSetupMode.QUICK_SETUP).flatMap(it->it).takeUntil(unsubscribeOperation),
(bytes, bytes2) -> {
unsubscribeOperation.onNext(true);
return Pair.create(bytes,bytes2);
}
);
}
In above code, I am zipping two indication operations and once I get the value from it I am unsubscribing from the change chain using PublishSubject and takeUntil.
Currently I have a service that returns that returns a list of parameters. if there are 4 parameters I need to perform one request per parameter to the same endpoint using the each of the parameter. After that I need to save the list of results of all the request into a collection. If I don't know how many request do I have to perform, What rxJava operator I need to use and how should I use it?? .
Take into account that I don't need to wait for the answer of the first request to perform the second one and ....
I have seen that the zip operator allow me to perform parallel request but I have to know the number of request to use it.
You can use flatMap to create Observable for each parameter and execute them in parallel as in
Observable.fromArray(parameters)
.flatMap(val -> Observable.just(val)
.subscribeOn(Schedulers.io())
.map(request -> doApiCall(request))
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> log(response));
At the end I implemented it this way:
public Subscription getElementsByStage(List <String> requiredStages) {
List < Observable <ElementsResponse>> observables = new ArrayList < > ();
for (String stage: requiredStages) {
ElementsRequest request = buildElementRequest(stage);
observables.add(request).subscribeOn(Schedulers.newThread()));
}
Observable zippedObservables = Observable.zip(observables, this::arrangeElementsByStage);
return zippedObservables
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber <HashMap<Stage,Element>>() {
.....
}
}
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.
I want to implement place autocomplete in Android application, and for this I'm using Retrofit and RxJava. I want to make response every 2 seconds after user type something. I'm trying to use debounce operator for this, but it's not working. It's giving me the result immediately without any pause.
mAutocompleteSearchApi.get(input, "(cities)", API_KEY)
.debounce(2, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(prediction -> Observable.fromIterable(prediction.getPredictions()))
.subscribe(prediction -> {
Log.e(TAG, "rxAutocomplete : " + prediction.getStructuredFormatting().getMainText());
});
As #BenP says in the comment, you appear to be applying debounce to the Place Autocomplete service. This call will return an Observable that emits a single result (or error) before completing, at which point the debounce operator will emit that one and only item.
What you probably want to be doing is debouncing the user input with something like:
// Subject holding the most recent user input
BehaviorSubject<String> userInputSubject = BehaviorSubject.create();
// Handler that is notified when the user changes input
public void onTextChanged(String text) {
userInputSubject.onNext(text);
}
// Subscription to monitor changes to user input, calling API at most every
// two seconds. (Remember to unsubscribe this subscription!)
userInputSubject
.debounce(2, TimeUnit.SECONDS)
.flatMap(input -> mAutocompleteSearchApi.get(input, "(cities)", API_KEY))
.flatMap(prediction -> Observable.fromIterable(prediction.getPredictions()))
.subscribe(prediction -> {
Log.e(TAG, "rxAutocomplete : " + prediction.getStructuredFormatting().getMainText());
});
I'm playing around with RXJava, retrofit in Android. I'm trying to accomplish the following:
I need to poll periodically a call that give me a Observable> (From here I could did it)
Once I get this list I want to iterate in each Delivery and call another methods that will give me the ETA (so just more info) I want to attach this new info into the delivery and give back the full list with the extra information attached to each item.
I know how to do that without rxjava once I get the list, but I would like to practice.
This is my code so far:
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.map(tick -> RestClient.getInstance().getApiService().getDeliveries())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(deliveries -> {
MainApp.getEventBus().postSticky(deliveries);
});
This is giving me a list of deliveries. Now I would like to accomplish the second part.
Hope I been enough clear.
Thanks
Finally I found a nice way to do it.
private void startPolling() {
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.flatMap(tick -> getDeliveriesObs())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(this::parseDeliveries, Throwable::printStackTrace);
}
private Observable<List<Delivery>> getDeliveriesObs() {
return RestClient.getInstance().getApiService().getDeliveries()
.flatMap(Observable::from)
.flatMap(this::getETAForDelivery)
.toSortedList((d1, d2) -> {
if (d1.getEta() == null) {
return -1;
}
if (d2.getEta() == null) {
return 1;
}
return d1.getEta().getDuration().getValue() > d2.getEta().getDuration().getValue() ? 1 : -1;
});
}
Let's go step by step.
First we create an Observable that triggers every POLLING_INTERVAL time the method getDeliveriesObs() that will return the final list
We use retrofit to get an Observable of the call
We use flatMap to flattern the resut list and get in the next flatmap a Delivery item, one by one.
Then we get the estimated time of arrival set inside the Delivery object and return it
We sort the list to order by estimated time of arrival.
In case of error we print and retry so the interval does not stop
We subscribe finally to get the list sorted and with ETA inside, then we just return it or whatever you need to do with it.
It's working properly and it's quite nice, I'm starting to like rxjava :)
I haven't spent a lot of time with Java 8 lambdas, but here's an example of mapping each object to a different object, then getting a List<...> out at the other end in plain ol' Java 7:
List<Delivery> deliveries = ...;
Observable.from(deliveries).flatMap(new Func1<Delivery, Observable<ETA>>() {
#Override
public Observable<ETA> call(Delivery delivery) {
// Convert delivery to ETA...
return someEta;
}
})
.toList().subscribe(new Action1<List<ETA>>() {
#Override
public void call(List<ETA> etas) {
}
});
Of course, it'd be nice to take the Retrofit response (presumably an Observable<List<Delivery>>?) and just observe each of those. For that we ideally use something like flatten(), which doesn't appear to be coming to RxJava anytime soon.
To do that, you can instead do something like this (much nicer with lambdas). You'd replace Observable.from(deliveries) in the above example with the following:
apiService.getDeliveries().flatMap(new Func1<List<Delivery>, Observable<Delivery>>() {
#Override
public Observable<Delivery> call(List<Delivery> deliveries) {
return Observable.from(deliveries);
}
}).flatMap(...)