I am using RXAndroidBle library to connect and read data from the BLE device. I have set the establishConnection function to true i.e. auto-connect to true. when the BleAlreadyConnectedException is occurring I want to capture that exception and restart the flow of reading data because every time disposing and connecting to BLE device is creating issues. so better to keep the connection alive and re-read the data.
In onErrorResumeNext i to re-call the functions writeStatus, readModelInfo,getReadings and so on. Now sure how would I achieve it.
device.establishConnection(true)
.flatMap(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
return Observable.just(rxBleConnection);
})
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(TSDictionary.BATTERY_LEVEL,NotificationSetupMode.QUICK_SETUP).flatMap(it->it))
.flatMap(bytes -> writeStatus())
.flatMap(bytes->readModelInfo(bytes))
.flatMap(bytes -> getReadings(bytes))
.doOnNext(data->initializeErrorHistory(data))
.flatMap(data->getSequenceSize())
.flatMap(length ->getOperationInfo(length))
.doOnNext(data->initializeOperationInfo(data))
.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends ArrayList<Map<Integer, TSDictionaryMetaData>>>>() {
#Override
public ObservableSource<? extends ArrayList<Map<Integer, TSDictionaryMetaData>>> apply(#io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
if(throwable instanceof BleAlreadyConnectedException){
// i want to RECALL/restart the function call
// writeStatus ,readModelInfo,getReadings, initializeErrorHistory
// getSequenceSize , getOperationInfo, initializeOperationInfo
}
return null;
}
})
.subscribe(data -> {
}, e -> {
e.printStackTrace();
});
Put the onErrorResumeNext closer to the connection code.
device.establishConnection(true)
.doOnNext(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
})
.onErrorResumeNext(throwable -> {
if (throwable instanceof BleAlreadyConnectedException) {
return Observable.just(mRxBleConnection);
}
return Observable.error(throwable);
})
.flatMap(rxBleConnection ->
rxBleConnection.setupNotification(TSDictionary.BATTERY_LEVEL,
NotificationSetupMode.QUICK_SETUP)
.flatMap(it->it)
)
.flatMap(bytes -> writeStatus())
.flatMap(bytes->readModelInfo(bytes))
.flatMap(bytes -> getReadings(bytes))
.doOnNext(data->initializeErrorHistory(data))
.flatMap(data->getSequenceSize())
.flatMap(length ->getOperationInfo(length))
.doOnNext(data->initializeOperationInfo(data))
.subscribe(data -> {
}, e -> {
e.printStackTrace();
});
Related
I have a chain of API calls in RxJava, and when one fails I need to abort the chain and handle the error. But each failure needs to be handled differently. I tried this:
netRequestOne()
.onErrorResumeNext {
handleErrorOne()
Single.error(it)
}
.flatMap {
netRequestTwo()
}
.onErrorResumeNext {
handleErrorTwo()
Single.error(it)
}
// more flatMaps with requests...
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
// deal with success
},
{
// no-op
}
)
But if a request throws an exception, all of the subsequent onErrorResumeNext()'s are called, not just the one tied to that request.
You can achieve the requested behavior for example with this:
val requestOne = Single.just("Response 1")
//.doOnSuccess { throw RuntimeException("Fail second request") } // (1)
.doOnError {
println("handleErrorOne")
}
val requestTwo = Single.just(10)
//.doOnSuccess { throw RuntimeException("Fail second request") } // (2)
.doOnError {
println("handleErrorTwo")
}
requestOne
.flatMap { oneResult -> requestTwo.map { twoResult -> Pair(oneResult, twoResult) } }
.doOnSuccess { responses: Pair<String, Int> ->
println(responses)
}
.flatMap { Single.just("More flatMaps") }
.subscribe({}, {})
You can uncomment (1) and/or (2) to simulate a fail in the first or second request. In case, both requests end successfully, responses are combined and you do some other processing.
I want to implement offline-last approach with Flow, first try to fetch data from remote source if it fails, for instance Retrofit throwing network exception, i want to fetch data from local source with the code below
return flow { emit(repository.fetchEntitiesFromRemote()) }
.map {
println("π getPostFlowOfflineLast() First map in thread: ${Thread.currentThread().name}")
val data = if (it.isEmpty()) {
repository.getPostEntitiesFromLocal()
} else {
repository.deletePostEntities()
repository.savePostEntity(it)
repository.getPostEntitiesFromLocal()
}
entityToPostMapper.map(data)
}
.catch { cause ->
println("β getPostFlowOfflineLast() FIRST catch with error: $cause, in thread: ${Thread.currentThread().name}")
flow { emit(repository.getPostEntitiesFromLocal()) }
}
.map { postList ->
println("π getPostFlowOfflineLast() Second map in thread: ${Thread.currentThread().name}")
ViewState<List<Post>>(
status = Status.SUCCESS,
data = postList
)
}
.catch { cause: Throwable ->
println("β getPostFlowOfflineLast() SECOND catch with error: $cause, in thread: ${Thread.currentThread().name}")
flow {
emit(
ViewState<List<Post>>(
Status.ERROR,
error = cause
)
)
}
}
But it gets stuck with exception
I: β getPostFlowOfflineLast() FIRST catch with error: java.net.UnknownHostException: Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname, in thread: main
What should be the right implementation to have any observable like with RxJava onResumeNext if repository function was an Observerable?
onErrorResumeNext { _: Throwable ->
Observable.just(repository.getPostEntitiesFromLocal())
}
Figured out that i can use emitAll with a flow to continue flow even multiple times.
.catch { cause ->
println("β getPostFlowOfflineLast() FIRST catch with error: $cause, in thread: ${Thread.currentThread().name}")
emitAll(flow { emit(repository.getPostEntitiesFromLocal()) })
}
.map {
if (!it.isNullOrEmpty()) {
entityToPostMapper.map(it)
} else {
throw EmptyDataException("No data is available!")
}
}
.map { postList ->
println("π getPostFlowOfflineLast() Third map in thread: ${Thread.currentThread().name}")
ViewState(status = Status.SUCCESS, data = postList)
}
.catch { cause: Throwable ->
println("β getPostFlowOfflineLast() SECOND catch with error: $cause, in thread: ${Thread.currentThread().name}")
emitAll(flow { emit(ViewState(Status.ERROR, error = cause)) })
}
I'm trying to recover from errors using RxJava and GRPC. This is my observable:
Observable<Object> observable = Observable.fromCallable(() -> {
try {
Grpc.MyRequest request = Grpc.MyRequest.newBuilder()
.setToken(mToken)
.build();
Grpc.MyResponse reply = mStub.mytest(request);
return reply;
} catch (Exception e) {
///
}
}).cache();
And this is the subscription:
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
})
.subscribe((result) -> {
MyResponse res = ((MyResponse) result);
if (res.getCode()!=0) {
//Check error code and try to refresh token and repeat this request after.
}
},throwable -> {
throwable.printStackTrace();
});
So, when I get the error from my GRPC service, depending on the error code, I want to try and recover from it by doing another request, and then repeating the original request. I'm not sure how to use RxJava retrywhen.
What is the most elegant way of doing something like this?
Error recovery in an observer chain does require a bit of tap dancing, and is by no means elegant. However, it can be contained in the observer chain.
boolean isRecoverable( Throwable t ) {
// this test can be as sophisticated as you want
if ( t instanceof StatusRuntimeException ) {
return true;
}
return false;
}
...
.retryWhen( throwableObservable ->
throwableObservable.flatMap( t -> isRecoverable( t )
? Observable.just("")
: Observable.error( t ) )
...
This approach allows you to decide what you want to do with the error. You could add a delay the just() so that you don't retry immediately. Instead of the just(), you could return an Observable that fetches a new API token.
I am using RxAndroidBle library for handling BLE connection and reading/writing to GATT server from my android gatt client app. I have followed the sample application provided on github.
The problem I am facing is my GATT server is running on Intel Edison and it is supporting MTU size of 80 only .It sends data in chunks, I am supposed to read the charcterstics value multiple time until i encounter a special character, something like '/END' . I have tried Custom read operation example which is supposed to read 5 times every 250 ms.
private static class CustomReadOperation implements RxBleRadioOperationCustom<byte[]> {
private RxBleConnection connection;
private UUID characteristicUuid;
CustomReadOperation(RxBleConnection connection, UUID characteristicUuid) {
this.connection = connection;
this.characteristicUuid = characteristicUuid;
}
/**
* Reads a characteristic 5 times with a 250ms delay between each. This is easily achieve without
* a custom operation. The gain here is that only one operation goes into the RxBleRadio queue
* eliminating the overhead of going on & out of the operation queue.
*/
#NonNull
#Override
public Observable<byte[]> asObservable(BluetoothGatt bluetoothGatt,
RxBleGattCallback rxBleGattCallback,
Scheduler scheduler) throws Throwable {
return connection.getCharacteristic(characteristicUuid)
.flatMap(characteristic -> readAndObserve(characteristic, bluetoothGatt, rxBleGattCallback))
.subscribeOn(scheduler)
.takeFirst(readResponseForMatchingCharacteristic())
.map(byteAssociation -> byteAssociation.second)
.repeatWhen(notificationHandler -> notificationHandler.take(5).delay(250, TimeUnit.MILLISECONDS));
}
#NonNull
private Observable<ByteAssociation<UUID>> readAndObserve(BluetoothGattCharacteristic characteristic,
BluetoothGatt bluetoothGatt,
RxBleGattCallback rxBleGattCallback) {
Observable<ByteAssociation<UUID>> onCharacteristicRead = rxBleGattCallback.getOnCharacteristicRead();
return Observable.create(emitter -> {
Subscription subscription = onCharacteristicRead.subscribe(emitter);
emitter.setCancellation(subscription::unsubscribe);
try {
final boolean success = bluetoothGatt.readCharacteristic(characteristic);
if (!success) {
throw new BleGattCannotStartException(bluetoothGatt, BleGattOperationType.CHARACTERISTIC_READ);
}
} catch (Throwable throwable) {
emitter.onError(throwable);
}
}, Emitter.BackpressureMode.BUFFER);
}
private Func1<ByteAssociation<UUID>, Boolean> readResponseForMatchingCharacteristic() {
return uuidByteAssociation -> uuidByteAssociation.first.equals(characteristicUuid);
}
}
and i am calling it like this
public void customRead()
{
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.queue(new CustomReadOperation(rxBleConnection, UUID_READ_CHARACTERISTIC)))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
configureMvpView.showList(bytes);
}, this::onRunCustomFailure);
}
}
and i am not getting any data from the server using this code.
However if i try simple read operation like this
public void readInfo() {
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(UUID_READ_CHARACTERISTIC))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
// parse data
configureMvpView.showWifiList(bytes);
}, this::onReadFailure);
}
}
I get the first chunk of data, but i need to read rest of data.
I am not very well versed with RxJava. So there might be an easy way to do this, But any suggestion or help will good.
This is my prepareConnectionObservable
private Observable<RxBleConnection> prepareConnectionObservable() {
return bleDevice
.establishConnection(false)
.takeUntil(disconnectTriggerSubject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(this::clearSubscription)
.compose(this.bindToLifecycle())
.compose(new ConnectionSharingAdapter());
}
I call
connectionObservable.subscribe(this::onConnectionReceived, this::onConnectionFailure);
and onConnectionReceived i call CustomRead.
You do not show how the connectionObservable is created and I do not know if anything else is done on that connection before the above code is executed.
My guess is that if you would look into the logs of the application you would see that the radio processing queue starts executing your CustomReadOperation as the first operation after the connection. In your custom operation you are calling RxBleConnection.getCharacteristic(UUID) which tries to execute .discoverServices() (schedule a RxBleRadioOperationDiscoverServices on the radio queue). The problem is that the radio queue is already executing your CustomReadOperation and will not discover services until it will finish.
There is a reason why RxBleConnection is not passed to the RxBleRadioOperationCustom.asObservable() β most of the functionality will not work at that time.
What you can do is to perform RxBleConnection.discoverServices() before scheduling your CustomReadOperation and pass the BluetoothGattCharacteristic retrieved from RxBleDeviceServices in the constructor. So instead of having this:
public void customRead()
{
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.queue(new CustomReadOperation(rxBleConnection, UUID_READ_CHARACTERISTIC)))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
configureMvpView.showList(bytes);
}, this::onRunCustomFailure);
}
}
You would have something like:
public void customRead()
{
if (isConnected()) {
connectionObservable
.flatMap(RxBleConnection::discoverServices, (rxBleConnection, services) ->
services.getCharacteristic(UUID_READ_CHARACTERISTIC)
.flatMap(characteristic -> rxBleConnection.queue(new CustomReadOperation(characteristic)))
)
.flatMap(observable -> observable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
configureMvpView.showList(bytes);
}, this::onRunCustomFailure);
}
}
Edit (clarification):
And the constructor of your CustomReadOperation should look like this:
CustomReadOperation(BluetoothGattCharacteristic characteristic) {
this.characteristic = characteristic;
}
So you will not have to use the this.rxBleConnection.getCharacteristic(UUID) inside of your CustomReadOperation and use directly bluetoothGatt.readCharacteristic(this.characteristic).
Edit 2:
Change these two lines:
return connection.getCharacteristic(characteristicUuid)
.flatMap(characteristic -> readAndObserve(characteristic, bluetoothGatt, rxBleGattCallback))
To this (the rest is the same):
return readAndObserve(this.characteristic, bluetoothGatt, rxBleGattCallback)
I have a SearchView that executes a network request to search for some tracks and then populates a RecylerView with the results. I have found this code which works fine.
I have already integrated the RecyclerView EmptyView through the Adapter but now I am trying to integrate the LoadingView(Progress) and ErrorView inside this code. I tried to put the LoadingView(ProgressBar) on Visibility True inside the concatMap but got the error that the βOnly the original thread that created a view hierarchy can touch its views.β which can be solved running that on the MainThread but I am sure there is a better way to do this.
Can someone have a better idea where and how the logic about show/hide the ErrorView and LoadingView can be integrated into this code?
I am using also RxBinding. Maybe also using RxRecyclerView would be a good idea?
RxSearchView.queryTextChanges(searchView).
filter(charSequence ->
!TextUtils.isEmpty(charSequence))
.throttleLast(100, TimeUnit.DAYS.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.concatMap(searchTerm ->
{
return searchTracks(searchTerm).
.subscribeOn(Schedulers.io())
.onErrorResumeNext(throwable1 -> {
//handle error somehow, change UI
return Observable.empty();
}
);
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tracks -> {
populateTracks(tracks);
}
});
}, throwable -> {
//show errorView
});
This here was my complete solution without stripping code for the presentation.
RxSearchView.queryTextChanges(searchView)
.skip(1)
.doOnNext(charSequence -> Log.v(TAG, "searching: " + charSequence))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(charSequence -> {
final boolean empty = TextUtils.isEmpty(charSequence);
if (empty) {
Log.v(TAG, "empty view");
mAdapter.clear();
}
return !empty;
})
.concatMap(charSequence -> {
Log.v(TAG, "requesting " + charSequence);
return onErrorResumeNext(
mGitApiService.searchRepositoriesSingle(charSequence.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
throwable -> {
try {
throw throwable;
} catch (HttpException httpException) {
showEmptyErrorView(httpException.message());
} catch (Throwable other) {
showEmptyErrorView(other.getMessage());
other.printStackTrace();
}
return Observable.empty();
});
})
.doOnNext(charSequence -> Log.v(TAG, "got data"))
.subscribe(response -> {
showRepositories(response.getItems());
}, throwable -> {
throwable.printStackTrace();
showEmptyErrorView(throwable.getMessage());
});
so basically whenever you touch your view you have to call .observeOn(AndroidSchedulers.mainThread())