I have two async methods, that got to be called while one operation. Each method could be completed successfully or retrieve with error. On case of error, I got to retry call each method once again, with delayed of 2 sec. Mean, I should call both methods, despite of result of one of them. In error callback I want to know in which method error occured, or in both methods.
It seems I should use Completable for this, but I'm absolutely newbie in Rx.
private void method1(final CompletableEmitter e, String path){
Database.getInstance().getReference(path).addListener(new Listener() {
#Override
public void onDataChange(Data data) {
//todo something
e.onComplete();
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(new Throwable(databaseError.getMessage()));
}
});
}
Method2 is the same.
The following code doesn't work properly.
Completable completable1 = Completable.create(method1(e););
Completable completable2 = Completable.create(method2(e););
completable1
.doOnError(…)
.retry(1)
.andThen(completable2 //never called if completable1 gets onError each time
.retry(1)
.doOnError(…))
.subscribe(…).dispose();
You have a lot of ways to do this. I'm going just to limit to explain how to achieve this using two Completables
Let's say you have two completables:
Completable doSomething = ...
Completable doSomethingElse = ...
To execute these sequentially,
you can concatenate them using andThen operator. Then to delay a retry when an error occurs, you can use retryWhen:
doSomething.andThen(doSomethingElse)
.retryWhen { Flowable.timer(2, TimeUnit.SECONDS) }
.subscribe()
This snippet above will retry infinitely if an error is permanently occurring. To go beyond, you can limit the number of tries using:
.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
If you want to retry only when a given type of error occurs, you can use:
.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (it is YourSpecficError && retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
In the case you want to retry each one independently, you can use:
doSomething.retryWhen { ... }
.andThen(doSomethingElse.retryWhen { ... })
.subscribe()
In addition, in order to avoid the retryWhen logic duplication, you could encapsulate this in an extension function:
fun Completable.retryDelayed(): Completable {
return this.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (it is YourSpecficError && retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
}
If you want to run your completables in parallel you ca use merge operator:
Completable doAll = Completable.merge(listOf(doSomething, doSomething))
Related
my Flowable is returned from Retrofit ApiService, i attempt the below code to implement error retry mechanism, i wish it can call onError function when occurred error at last retry.
flowable.retryWhen(throwableFlowable -> {
AtomicInteger retryCounter = new AtomicInteger();
return throwableFlowable
.takeWhile(throwable -> retryCounter.getAndIncrement() < 3);
})
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onNext, onError, onComplete, onSubscribe);
but it doesn't call the onError function when occurred error at last retry. I try add doOnError(onError) before retryWhen, but it doesn't work. Could anyone help me?
the RxJava is 2.2.19, the RxAndroid version is 2.1.1.
You need to make the inner flow signal an onError. If you want this to be the last error from the main sequence, rethrow it in takeWhile:
.takeWhile(throwable -> {
if (retryCounter.getAndIncrement() < 3) {
return true;
}
throw throwable;
})
Edit:
Alternatively, you could replace takeWhile with flatMap and signal a Flowable.error.
flowable.retryWhen(throwableFlowable -> {
AtomicInteger retryCounter = new AtomicInteger();
return throwableFlowable
.flatMap(throwable -> {
if (retryCounter.getAndIncrement() < 3) {
return Flowable.just(1);
}
return Flowable.<Integer>error(throwable);
});
})
How can I achieve that doOnNext wait to the results of multiple asynchronous tasks?
For example -
public void getImages(User user) {
Flowable.create(new FlowableOnSubscribe<User>() {
#Override
public void subscribe(#io.reactivex.rxjava3.annotations.NonNull FlowableEmitter<User> emitter) throws Throwable {
emitter.onNext(user);
}
}, BackpressureStrategy.BUFFER)
.observeOn(Schedulers.io())
.doOnNext(user -> {
ArrayList<String> imagesUrls = user.getUrls();
for (String url : imagesUrls) {
storage.getReference().child("images").child(url).getBytes(ParametersConventions.FIREBASE_DOWNLOAD_IMAGE_MAX_SIZE).
addOnSuccessListener(bytes -> {
doSomething(bytes);
});
}
})
.doOnNext(user -> {
doSomething();
})
.doOnComplete(...);
}
and I want that the doOnNext which calls to doSomething will be called after all the asynchronous calls to download the images are finished.
Turn that API call into a reactive type and merge it into the main flow:
int max = ParametersConventions.FIREBASE_DOWNLOAD_IMAGE_MAX_SIZE;
public Completable downloadAsync(URL url) {
return Completable.create(inner -> {
storage.getReference()
.child("images")
.child(url)
.getBytes(max)
.addOnSuccessListener(bytes -> {
doSomething(bytes);
inner.onComplete();
});
});
}
Together:
Flowable.create(emitter-> {
emitter.onNext(user);
}, BackpressureStrategy.BUFFER)
.observeOn(Schedulers.io())
.concatMapSingle(user ->
Flowable.fromIterable(user.getUrls())
.concatMapCompletable(url -> downloadAsync(url))
.andThen(Single.just(user))
)
.doOnNext(user -> {
doSomething();
})
.doOnComplete(...);
doOnNext operator is fired every time there is a new item on a stream so it is not the best option for you. Try using map/flatMap/concatMap operator depending on your needs. If you need to make several calls and then do something with the data you can look at similar question I've already answered link: Chaining API Requests with Retrofit + Rx
in which you can find a way to make sequential network calls and then do whatever you want with a list of data :D
I have something like:
private Single<List<Data>> getFirstApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Single<AnotherData> getSecondApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public void execute() {
//Here I need to run both observables one by one, and show result of each in View
// Code exetuting both
.subscribe(......);
}
How can I run two observables and subscribe on them in last method. In other words, I need to run method execute which will display in UI result of each Observables.
By the way, Observable not connected, they fetch different data (so I can run them asynchronous)
One way to do that is with flatMap:
public void execute() {
getFirstApiResponse()
.flatMap(response1 -> {
// getFirstApiResponse has completed
// ...
return getSecondApiResponse();
})
.subscribe(response2 -> {
// getSecondApiResponse has completed
// ...
}, error -> {
// One of the other operation has failed
});
}
You could look into the zip operator as well, depending on your needs. The downside to this solution is you are forced to combine your responses into a pair or another suitable datastructure, which may not make sense for you.
public void execute() {
Single.zip(getFirstApiResponse(), getSecondApiResponse(),
(first, second) -> {
//now you have both
return Pair.of(first, second);
}).subscribe(pair -> {/**do stuff**/});
}
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'm using a travel API which I first hit with a request to create a session and then I use the session URL returned from that URL to then call until its status parameter returns UpdatesComplete.
Here's what I have so far:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.doOnNext {
if (it.status != "UpdatesComplete") travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.subscribe({
// Subscription stuff
)}
What's currently happening is that it will call doOnNext() and then it will make the network poll but I won't be capturing the subscription and also won't chain another poll. Is there a more efficient way I can be writing this?
Solution
Thanks to iagreen I managed to achieve this with:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
travelInteractor.pollResults(url)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.repeatWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.filter {
it.itineraries.map { ... } // Use response here appropriately and then check status
it.status == "UpdatesComplete"
}
.take(1)
}
.subscribe({
// Subscription stuff
)}
I am assuming your pollResults(url) method returns a Single or an Observable that behaves like a single -- it returns one result and then onComplete. If that is truly the case, you can use repeatWhen to retry the request on success and retryWhen to retry on error. See the code below.
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
skyScannerInteractor.pollResults(url)
.repeatWhen { complete -> complete.delay(1, TimeUnit.SECONDS) }
.retryWhen { errors -> errors.delay(1, TimeUnit.SECONDS) }
.filter({ it.status == "UpdatesComplete" })
.take(1) // Take the first valid value and complete
}
.subscribe({
// Subscription stuff
)}
A little explanation -
repeatWhen/retryWhen will try the request every second.
filter will cause elements with the wrong status to be ignored.
When you get the first status == "UpdatesComplete" element, take(1) will emit that value and complete -- this will have the effect of cancelling the retries.
Note: In the case of an error, retrying a network request forever is usually the wrong thing to do. I recommend you modify the retryWhen above to suit you use case to terminate in the event of network failure. For example, you could retry three times and then propagate the error. See this article for some examples on how you could do that. It is also a good reference on repeatWhen/retryWhen.
retry() and retryWhen() both respond to an onError event in the Observable, that is why it isn't actually retrying; you're not receiving those onError events in your pollResults() Observable. Right now, your retrying code doesn't actually depend on the JSON response.
There are two ways I would think about going about this:
Throw an exception in your pollResults() Observable if the JSON response is unsatisfactory. This should trigger the retryWhen(). You'll need to test for it in the Observable somewhere.
Reorganize your Observable like this:
`
lateinit var pollUrl: String
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
skyScannerInteractor.pollResults(pollUrl)
}
.doOnNext {
if (it.status != "UpdatesComplete") {
throw IOException("Updates not complete.") //Trigger onError
}
}
.retryWhen { //Retry the Observable (createSession) when onError is called
it.delay(1000, TimeUnit.MILLISECONDS)
}
.subscribe({
// Will give result only when UpdatesComplete
}