RxJava linear backoff, passing in earlier date parameters on retry - android

I'm refactoring some code that would call a web service with a formatted date parameter ("2016-3-10" for example), and if that returned null, would fire off another method with a date one day earlier (like "2016-3-9"). This would happen for at least 3 retries.
I'm refactoring this into RxJava and am not sure how to implement the backoff strategy or which .retry() or .retryWhen() operator to use in this situation.
I have the web service returning the usual Observable using Retrofit.
This is what I have currently:
PictureService pictureService = retrofit.create(PictureService.class);
pictureService.getPhotos(date)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry() //Some retry
...
.subscribe()
So which operator would be best used here for retrying the requests, and how should I pass in a new date to the getPhotos() request when the current date returns null?
Just for the sake of completion here is the Retrofit service that was mentioned above:
#GET("/someurl/photos?api_key=xxxxxxx")
Observable<PictureAPI> getPhotos(#Query("earth_date") String earthDate);

OK, now that I can answer this question again, here we go:
Observable.just("2016-3-10", "2016-3-9", "2016-3-8")
.concatMap(date -> pictureService.getPhotos(date))
.filter(response -> response != null)
.take(1)...
For a detailed explanation take a look at this blog post by Dan Lew: http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/
In short: concat (and concatMap) will subscribe to each new Observable only after the previous one has emitted onCompleted AND only if more items are needed downstream. The take(1) will unsubscribe after the first non-null response and therefore concatMap will just not subscribe to the next Observable.
EDIT: To check that really only one request is sent you could
1.) Enable logging in Retrofit (if you are recent version by adding an Interceptor to the OkHttpClient). This lets you directly observe the requests that are being sent.
2.) Add a doOnSubscribe() like this:
.concatMap(date -> pictureService.getPhotos(date)
.doOnSubscribe(new Action0() {
#Override
public void call() {
Log.d(TAG, "sending request for " + date);
}
});
)
Since Retrofit requests are only sent upon subscription the log message will appear if and only if a request is then sent.

Related

Observable 'wait' for another observable for access token and resubscribe

I am stuck on a problem and the situation is that I am calling Observable getFilms() and when the token has expired and it throws an onError I would like to ask another Observable for refresh token 'wait' and have this getFilms() method resubscribe.
I tried retryWhen but it is called 3 times which is not what I want and it doesn't give the desired effect even with setting the firstAttempt condition
getFilms() .hereWaitForRefreshTokenObservable() .~ .subscribe(films ->)
If anyone gets here the solution to this is to use Authenticator correctly.
In my case I doubted its performance and spent a couple of hours trying to fix it in the RXJava layer.
I simply forgot to add the "Bearer" :
.header("Authorization", "Bearer $newToken")
Observable 'wait' for another observable for access token and resubscribe
Answer to the question in the title:
getFilms()
.onErrorResumeNext { ex ->
if(ex is HttpException && ex.code() == 401){
reauthorize()
.flatMap {
getFilms()
}
} else {
Observable.error(ex)
}
}
That's a very common pattern when using expiring access, like JWT.
Side note: I assume we're talking about http calls and in such case you probably should use Maybe (eventually Single or Completable) but not Observable. Observable is for sequences returning more than 1 element and a http call usually can't return more than 1 response. Not unless you have some very advanced cache that common clients lack.

Best way to do an RxJava call based on the results of the previous API call

I'm doing an API call to get the descriptions of a program podcast and based on the type of podcast, I may or may not have to do another API call to get more information. I'm new to RxJava and I'm not sure the best way to make such calls. Here's what I have so far:
public void fetchFeaturedItems() {
Timber.i("Fetching Featured...");
disposables.add(episodeService.getFeatured()
.subscribeOn(Schedulers.io())
.doOnNext(featured -> { //make second call
final Episode episode = featured.getEpisode();
Observable<Timing> timingObservable = episodeService.getTimingForEpisodeActs(episode);
if (timingObservable != null) {
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);}); //save to database
}
})
.observeOn(Schedulers.io())
.subscribe(featured -> {
saveFeatured(featured);
final Episode episode = featured.getEpisode();
notificationManager.handleNewEpisodeNotification(episode);
}, Timber::e));
}
This all works, but I'm getting a "result of subscribe is not used" lint warning on the second subscribe. I'm not combining results of the two calls. I could really use some guidance.
Use flatMap() instead of onNext(). You get warning about "result of subscribe is not used" cause of second subscribtion. flatMap() should help.
read this first and other RxJava documentation
.doOnNext is a side-effect operator. What you're doing:
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);});
Will just create a disposable. This disposable won't be a part of the stream. Also note that the timingObservable stream will now run totally independently, because as I just said, doOnNext is a side-effect operator. What you're doing is a fire-and-forget call. To make the response as a part of the stream, use a .flatMap in place of .doOnNext. It will merge your responses as they come, and push them to the downstream operators.
Ideally, a reactive stream should be subscribed to only once - you are doing it twice. This is an immediate code smell.

Rxjava booleanSupplier expected

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.

Send multiple requests using RxJava

I am very new to using RxJava with Retrofit in Android. I have successfully written the API calls and developed the interface too. Now, I want to write my code in a way that I can send two requests: second request depending upon the values of first request. Can someone guide me if this is possible? If so then how? Any code snippet will really be helpful.
For example: following are two requests:
mCompositeDisposable.add(fcService.getStationList()
.subscribeOn(Schedulers.io()) // "work" on io thread
.observeOn(AndroidSchedulers.mainThread()) // "listen" on UIThread
.subscribe(this::handleResults, this::handleError)
);
mCompositeDisposable.add(fcService.getStationSensor("12345678")
.subscribeOn(Schedulers.io()) // "work" on io thread
.observeOn(AndroidSchedulers.mainThread()) // "listen" on UIThread
.subscribe(this::handleResults, this::handleError)
);
Second request is possible with the value from the first request's response. Is it possible to merge these two requests in a way that I write code only once for them?
With the flatMap operator you can check the response of the first call and choose the next action to follow, in this way you build a new Observable that you can subscribe to (The next "code" is kotlin style):
Single<StationSensor> newSingle =
fcService.getStationList().flatMap{ stationList ->
when(stationList){
"OK_value" -> fcService.getStationSensor(stationList)
else -> Single.error(RuntimeException("Error response"))
}
}

Conditional chain of observables

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())
...

Categories

Resources