In an Android app scenario, I want to fetch some Observable<Data> from network, and there are multiple Observer<Data> subscribed to it to update corresponding views. In case of error -say a timeout- show a button to the user to try again.
How can I do the try again part? can I tell the observable to re-execute its logic again without re-subscribing to it?
Let's assume you have two buttons, "Retry" and "Cancel", initially hidden. Create two Observables retryButtonClicks and cancelButtonClicks. Then, apply the retryWhen operator to the designated download flow and act upon the signals of these button clicks:
download.retryWhen(errors -> {
return errors
.observeOn(AndroidSchedulers.mainThread())
.flatMap(e -> {
// show the "Retry" and "Cancel" buttons around here
return Observable.amb(
retryButtonClicks.take(1).map(v -> "Retry"),
cancelButtonClicks.take(1).map(v -> "Cancel")
)
.doOnNext(v -> { /* hide the "Retry" and "Cancel" buttons */ });
})
.takeWhile(v -> "Retry".equals(v))
;
});
There is actually specific methods
retry()
Returns an Observable that mirrors the source Observable,
resubscribing to it if it calls onError (infinite retry count).
and retry(long count)
Returns an Observable that mirrors the source Observable,
resubscribing to it if it calls onError up to a specified number of
retries.
Read more in an article and in the docs
Related
Hi i am trying to poll a request using rxjava repeatUntil but getting some error on it
below is my code
accountDelegator.signUpReady(signUpRequest)
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.repeatUntil { response ->
if (response.isSuccesfull){
return onComplete()
}
}
it says it requires a booleanSupplier not a unit?
i am simply trying to repeat the request above until i get a response.isSuccessful and then returning onComplete() as in the rxjava docs it states that to exit a repeatUntil observer, you have to call onComplete
repeatUntil does not provide any items to its BooleanSupplier function which function is expected to indicate whether or not to repeat the upstream. To "exit" it, you have to return true from the function as you can't call onComplete on anything there (nor does it make sense, you likely misinterpreted the documentation).
You could instead use filter and take which can be used to stop an otherwise repeating sequence:
accountDelegator.signUpReady(signUpRequest)
.subscribeOn(Schedulers.io())
.repeat(/* 100 */)
.filter { response -> response.isSuccessful }
.take(1)
.observeOn(AndroidSchedulers.mainThread());
You'd also want to limit the number of retries and/or delay the repetition by some time (so that your code doesn't spam the server just to not succeed) via repeatWhen.
Edit
To detail the last sentence about delayed retries, here is a way of doing that:
.repeatWhen { completion -> completion.delay(1, TimeUnit.SECONDS) }
instead of repeat(100). When the upstream completes, an object is signalled through completion which is then delayed by 1 seconds. After that, the other side in repeatWhen receives the object which triggers a resubscription to the upstream.
Here is a sample Rx chain using RxBindings:
RxView.clicks(refreshIcon)
.flatMap { Observable.error<Throwable>(IllegalArgumentException()) }
.subscribe(
{ Timber.d("onNext")},
{ error -> Timber.d("onError") })
.addTo(disposables)
After clicking my refresh icon once, this chain will not run again as a terminal event took place. I am guessing I was under the wrong impression that the subscription takes place whenever a click event is detected, and that it in fact subscribes whenever that block of code gets executed.
Question is how can I make my chain execute/re-execute on every click, even after it hits a terminal event? Looking for something intuitive.
Observable must complete when the first error occur, it's in their contract. In order to have your Observable survive terminal event, you will have to dig in RxJava Error handling operators. retry() seems a good fit in your case:
RxView.clicks(refreshIcon)
.flatMap { ... }
.retry()
.subscribe(...)
.addTo(disposables)
It is part of the Rx contract when an error occurred the stream will receive a onError event and will terminate.
Unless you actively handle the error, using for example: onErrorResumeNext()
I have an observable that on subscribe does a long operation but when a user click on a button I want to notify my observable to do again the long operation because something change.
I don't want to make a new subscription every time the user clicks on the button. Which is the best solution to achieve this?
I would like to know if is possible to use a solution, which use the rxjava simplified way to run code on different threads.
Should I use something like this?
BehaviourProcessor<boolean> processor = BehaviourProcessor.createDefault(true);
public Flowable<List<Item>> getItems(){
return Flowable.create(e -> e.onNext(longOp()))
.subscribeOn(Schedulers.io())
.switchMap(items -> processor.map(notify -> returnItems(notify)));
}
public void notifyChange(){
processor.onNext(true);
}
Android Room library achieve this result, in fact when you subscribe this:
#Query("SELECT * FROM user")
Flowable<List<Item>> getUsers();
Every time you delete an item from database you immediately get the new list from the database in the subscription on next method.
Rather than have getItems() return the observable chain that you have shown, return a shared observable.
Flowable<List<Item>> itemGetter =
Flowable.create(e -> e.onNext( longOp() ) )
.subscribeOn( Schedulers.io() )
.switchMap(items -> processor.map(notify -> returnItems(notify)))
.replay( 1 )
.publish();
Flowable<List<Item>> getItem() {
return itemGetter;
}
This creates only on observer chain that you can subscribe to as many times as you want. However, if there are no subscribers and another subscriber comes along, longOp() will be called again.
If you don't want that to happen, then you should consider using a BehaviorSubject<List<Item>> to cache the value.
I want to asynchronously retrieve data via multiple REST APIs. I'm using Retrofit on Android with the rxJava extension, i.e. I execute any GET request by subscribing to an Observable.
As I said, I have multiple source APIs, so when the first source does not yield the desired result I want to try the next on, if that also fails, again try the next and so forth, until all sources have been queried or a result was found.
I'm struggling to translate this approach into proper use of Observables since I don't know which operators can achieve this behaviour and there are also some constraints to honor:
when a result has been found, the remaining APIs, if any, should not be queried
other components depend on the result of the query, I want them to get an Observable when starting the request, so this Observable can notify them of the completion of the request
I need to keep a reference to aforementioned Observable because the same request could possibly be made more than once before it has finished, in that case I only start it the first time it is wanted and subsequent requests only get the Observable which notifies when the request finished
I was starting out with only one API to query and used the following for the request and subsequent notification of dependent components:
private Observable<String> loadData(int jobId) {
final ConnectableObservable<String> result = Async
.fromCallable(() -> getResult(jobId))
.publish();
getRestRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
dataHolder -> {
if (dataHolder.getData() != null && !dataHolder.getData().isEmpty()) {
saveData(dataHolder.getData());
} else {
markNotFound(dataHolder);
}
},
error -> currentJobs.remove(jobId),
() -> {
currentJobs.remove(jobId);
result.connect();
});
return result;
}
This code was only called for the first request, the returned Observable result would then be saved in currentJobs and subsequent requests would only get the Observable without triggering the request again.
Any help is highly appreciated.
Assuming you have a set of observables that re-connect each time you subscribe:
List<Observable<Result>> suppliers = ...
Then you just need to do the logical thing:
Observable<Result> results = Observable
.from(suppliers)
.concatMap(supplier -> supplier)
.takeFirst(result -> isAcceptable(result))
.cache()
Use .onErrorResumeNext, and assuming that each service observable may return 0 or 1 elements use first to emit an error if no elements are emitted:
Observable<T> a, b, c;
...
a.first().onErrorResumeNext(t -> b.first())
.onErrorResumeNext(t -> c.first())
.onErrorResumeNext(t -> d.first())
...
Say I have 2 Observables (A & B) that are essentially network calls (using Retrofit to give context).
The current flow of the app is as follows:
A & B are kicked off at about the same time (asynchronously).
B is executed 0 or more times on user interaction
I have 3 different scenarios that I want to listen for given these 2 observables/api calls.
I want to know immediately when Observable A completes
I want to know immediately when Observable B completes
I want to know when both have completed
First off, is this a good use case for RxJava?
I know how to do each scenario individually (using zip for the last), though I don't know how to do all of them simultaneously.
If I subscribe to Observable A, A begins. If I subscribe to B, B begins. If A & B complete before I subscribe to zip(a, b), I could miss the event and never actually see this complete, right?
Any general guidance would be appreciated. My RxJava knowledge is pretty thin :P
You can achieve this using three different observable, one for each of your case.
As you'll have to share states between each observables, you'll have to convert retrofit cold observables to hot observable. (see here for more information on this topic)
ConnectableObservable a = service.callA().publish();
ConnectableObservable b = service.callB().publish();
a.subscribe((e) -> { /* onNext */ }, (ex) -> {/* onError */}, () -> {/* when A is completed */ });
b.subscribe((e) -> { /* onNext */ }, (ex) -> {/* onError */}, () -> {/* when B is completed */ });
a.mergeWith(b).subscribe((e) -> { /* onNext */ }, (ex) -> {/* onError */}, () -> {/* when A and B are completed */ });
a.connect(); // start subscription to a
b.connect(); // start subscription to b
Do not share an object between onCompleted methods or you'll have to deal with concurrencies issues.