What is the proper way to write multiple characteristics with RxAndroidBle? - android

I'm new to Rx and still trying to figure out how to properly handle observables. I was wondering if there is a better way to write multiple characteristics than to do them one at a time with RxAndroidBle? Currently I'm doing them one at a time with the code down below.
Observable<RxBleConnection> mConnectionObservable;
private void saveChanges(String serialNumber, Date date, MachineTypeEnum machineType, MachineConfig machineConfig) {
mWriteSubscription = mConnectionObservable
.flatMap(rxBleConnection -> Observable.merge(
getWrites(rxBleConnection, serialNumber, machineType, machineConfig, date)
))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
((MainActivity)getActivity()).back();
}, BleUtil::logError);
}
private Iterable<? extends Observable<? extends byte[]>> getWrites(RxBleConnection rxBleConnection,
String serialNumber,
MachineTypeEnum machineType,
MachineConfig machineConfig,
Date date) {
List<Observable<byte[]>> observables = new ArrayList<>();
observables.add(rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_WRITE_SERIAL_NUMBER,
Utf8Util.nullPad(serialNumber, 16).getBytes()).doOnError(throwable -> Log.e("Write", "serial failed", throwable)));
observables.add(rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_MACHINE_TYPE,
new byte[]{(byte) machineType.val()}).doOnError(throwable -> Log.e("Write", "machine type failed", throwable)));
observables.add(rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_CHARACTERISTIC,
MachineConfigBitLogic.toBytes(machineConfig)).doOnError(throwable -> Log.e("Write", "machine config failed", throwable)));
observables.add(rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.CurrentTime.Characteristics.UUID_CURRENT_TIME,
TimeBitLogic.bytesFor(date)).doOnError(throwable -> Log.e("Write", "date failed", throwable)));
return observables;
}
So I changed my old code to what is above which now uses merge but only one of the characteristics seems to update now.

I would use merge operator:
mConnectionObservable
.flatMap(rxBleConnection ->
Observable.merge(
rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.DeviceInformation.Characteristics.UUID_SERIAL_NUMBER,
Utf8Util.nullPad(serialNumber, 16).getBytes()
),
rxBleConnection.writeCharacteristic(
Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_MACHINE_TYPE,
new byte[]{(byte) machineType.val()}
))
.subscribe(bytes -> {/* do something*/}, BleUtil::logError);
Also you could pass a list of observables to that operator:
Instead of passing multiple Observables (up to nine) into merge, you
could also pass in a List<> (or other Iterable) of Observables, an
Array of Observables, or even an Observable that emits Observables,
and merge will merge their output into the output of a single
Observable

The RxAndroidBle library is serializing any BLE requests under the hood as BLE implementation on Android is mostly synchronous (though the Android vanilla API suggests that it is not).
Mergeing of the writes is a good approach though you need to be aware of what the merge operator does:
* You can combine the items emitted by multiple Observables so that they appear as a single Observable, by
* using the {#code merge} method.
So I changed my old code to what is above which now uses merge but only one of the characteristics seems to update now.
The reason for this behaviour may be how you consume the stream:
.subscribe(bytes -> {
Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
((MainActivity)getActivity()).back();
}, BleUtil::logError);
Whenever the bytes are emitted you are calling Activity.back(). .merge() operator does emit bytes for every write command that is executed. If you unsubscribe from the Subscription in i.e. .onPause() then it would be unsubscribed right after the first write is completed.
You could make your flow to wait until all writes are completed like this:
private void saveChanges(String serialNumber, Date date, MachineTypeEnum machineType, MachineConfig machineConfig) {
mWriteSubscription = mConnectionObservable
.flatMap(rxBleConnection ->
Observable.merge(
getWrites(rxBleConnection, serialNumber, machineType, machineConfig, date)
)
.toCompletable() // we are only interested in the merged writes completion
.andThen(Observable.just(new byte[0])) // once the merged writes complete we send a single value that will be reacted upon (ignored) in .subscribe()
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {
Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
((MainActivity)getActivity()).back();
}, BleUtil::logError);
}

Related

Sending data over the air and after completion end the operation using RxAndroidBLE

I am trying to send the 128 bytes of the block to BLE Controller using the RxAndroidBle library. the flow to send data from mobile to BLE controller is as follows
Connect with BLE Controller
Start OTA (sending 1)
Send CRC (of the data block)
Send data block
wait for 2 seconds
repeat step 3
END OTA (sending 2)
Here is snapshot of a code
.flatMap(rxBleConnection -> prepareWriting())
.flatMapIterable(otaMetaData -> otaMetaData)
.zipWith(Observable.interval(2, TimeUnit.SECONDS), (item, interval) -> item)
.doOnNext(metaData -> {
otaMetaData = metaData;
})
.map(otaMetaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CHECKSUM, otaMetaData.getCrcBlock()).toObservable();
})
.doOnNext(otaMetaData -> {
Log.e(TAG, "Writing CRC " + Arrays.toString(BLEUtils.toHex(otaMetaData.getCrcBlock())));
})
.map(bytes -> {
return mRxBleConnection.writeCharacteristic(OTA_DATA, otaMetaData.getDataBlock()).toObservable();
})
.doOnNext(otaMetaData -> {
Log.e(TAG, "Writing Data " + Arrays.toString(BLEUtils.toHex(otaMetaData.getDataBlock())));
})
.flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END).toObservable())
The problem is while sending the END OTA because as the flatMapIterable returns 20 items, .flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END) is getting called 20 times.
So, I am not sure how I can send the OTA_DATA_END command when all the 20 items get processed. Moreover, any suggestion to improve the existing code is welcome.
You can use flatMapIterable() with toList(). Try to add toList() operator before OTA_DATA_END command like:
.toList() // wait for all commands to complete
.flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END).toObservable())
EDIT
Better to separate steps like
.flatMap(rxBleConnection -> prepareWriting())
.flatMap(otaMetaData -> Observable.fromIterable(otaMetaData)
.zipWith(Observable.interval(2, TimeUnit.SECONDS), (metaData, interval) -> metaData)
.flatMap(metaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CHECKSUM, metaData.getCrcBlock())
.toObservable();
}, (metaData, bytes) -> metaData) /* result selector */
.flatMap(metaData -> {
return mRxBleConnection.writeCharacteristic(OTA_DATA, metaData.getDataBlock())
.toObservable();
}, (metaData, bytes) -> metaData)
.toList()
.toObservable()
)
.flatMap(otaMetaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CONTROL, OTA_DATA_END)
.toObservable();
})

RxJava if/else with multiple chain calls

I have a webservice call that return an object in which there is a parameter that indicates whether the operation ended successfully or not, so I would like to filter it (kind of if/else statement) inside the RxJava chain by using RxJava operators. Is it possible?
Something like this but not using if/else:
repo.webserviceCall(username, password)
.flatMap(result -> {
if (result.isSuccessful())
repo.secondWebserviceCall(result.getInfo())
else
showToastMessage("Api call not successful"); //STOP FLOW HERE
})
.flatMap(result -> thirdWebserviceCall(res))
.subscribe(res -> {showSuccessMssg(res)}, throwable -> { showError(t)});
You can return an error() from your flatMap so that the execution then goes to the onError consumer in your subscribe call.
If each service call returns one item, you could rearrange the operators so that not successful won't run the flatMap for the second and third calls. The filter will turn the setup to empty for which you can use the onComplete handler to display the toast.
repo.webserviceCall(username, password)
.filter(result -> result.isSuccessful())
.flatMap(result ->
repo.secondWebserviceCall(result.getInfo())
.flatMap(result -> thirdWebserviceCall(res))
)
.subscribe(
res -> showSuccessMssg(res),
throwable -> showError(t),
() -> showToastMessage("Api call not successful")
);

Retrofit call within Stream

I have an edit text which allows a user to input a username and once the username is input the value is sent to the db to check whether the username already exists,if not then further operations are allowed else an error is displayed.
As of now this is my current code.
usernameObservable
.skip(2)
.debounce(800, TimeUnit.MILLISECONDS)
.subscribe(username -> {
Observable<Boolean> observable = apiService.isAvailable(username);
observable.observeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aBoolean -> {
}, throwable -> {
});
});
For now the network request is being made at the end but is it possible to make the request before and once i receive data i perform some other operations on the stream.
You are looking for flatMap operator. It allows you to transform an event into another observable, which will forward emissions to the original stream. You error notification will be forwarded as well.
usernameObservable
.skip(2)
.debounce(800, TimeUnit.MILLISECONDS)
.flatMap(username -> apiService.isAvailable(username))
.subscribe(isAvailableResult -> {
// react here
}, throwable -> {
// show an error here
});

Use RxJava and Retrofit to iterate through list and augment results based on subqueries

I'm using retrofit and I feel like rxjava (with retrolambda) would be a good fit for the following flow:
get list of widgets (http)
for each widget
a) get a list of articles (http) for the given widget type
b) save all those to db
c) take the first (latest) article in list and update widget.articleName and widget.articleUrl with appropriate values from this article
transform back to list and complete
However I'm unsure what to do after step 2a. Here's my code so far
apiService.getWidgets(token)
.flatMapIterable(widgets -> widgets)
.flatMap(widget -> apiService.getArticles(token, widget.type))
...
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
I've played around with some operators but when chaining, I always seem to narrow down
too far (e.g. get a handle on a single article) and then no longer have access to the
original widget to make modifications.
#GET("/widgets")
Observable<List<Widget>> getWidgets(#Header("Authorization") String token);
#GET("/articles")
Observable<List<Article>> getArticles(#Header("Authorization") String token, #Query("type") String type);
You could insert doOnNext at certain points of the stream to add side-effects:
apiService.getWidgets(token)
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
Here is runnable example of this.
adding this here since I couldn't find an example of iterating a list that is returned in an object as variable.
getUserAccount(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(userResponse -> Observable.just(userResponse.list)) //get list from response
.flatMapIterable(baseDatas -> baseDatas) //make the list iterable
.flatMap(baseData -> //on each project, get the details
getProjectDetails(baseData.name,token)
.subscribeOn(Schedulers.io()) //get network call off the main thread
.observeOn(AndroidSchedulers.mainThread()))
.subscribe(
(dataResponse) -> {
Timber.d( "Got Data Details:" + dataResponse);
},
(error) -> {
Timber.e( "Got Error:" + error.getMessage());
},
() -> {
Timber.d("Completed Data Details");
}
);
akarnokd's answer is quite helpful but that may cause NetworkOnMainThreadException.
To solve that I have added
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
on every requests
apiService.getWidgets(token)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);

RxJava infinite stream best practice

In android app i have this case:
Listen to my editText with observable:
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
Then i need to send network request with string emitted by observable:
.flatMap { networkObservable.subscribeOn(Schedulers.io()) }
My question is: what is the best possible way to write infinite stream of these network results.
Errors handled by UI.
Unsubscription done with AppObservable.bindActivity() wrapper
I ended up attaching materialize() operator to network observable, and then handling it like:
.subscribe{
when (it.getKind()) {
Kind.OnNext -> text.setText(it.getValue())
Kind.OnError -> text.setText(it.getThrowable().getMessage())
}
}
Do you know better way, or its just fine?
At least it works.
P.S. another useful case will be Refresh button clicks flatMap'ed to network calls
You can use onErrorResumeNext to recovery your Observable from a failure. E.g.,
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
.flatMap {
networkObservable.subscribeOn(Schedulers.io())
.onErrorResumeNext(t -> t.getMessage())
}

Categories

Resources