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.
Related
I'm refactoring the implementation of my repositories using RxJava so i want to know some ways to edit, for example, a user.
My getUser(email: String), with email as id, is returning an observable and in the repository implementation i either get the data from database or server, all good by now.
What i want to achieve is editing a user. For that i would have and update(user: User) function, and the naive way to use it would be
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.subscribe { user ->
user.name = "antoher name"
userRepository.update(user)
.subscribeOn(Schedulers.io())
.subscribe {
//handle response
}
}
Is there a way to avoid this type of call of an observer inside an observer? It is not very readable for me and i guess there's a better way but i'm not getting it.
NOTE: I'm using clean architecture, so i think an update for every field, making me get user in data module is not correct as i would have subscribe to an observer in data, and that difficult the dispose when activity destroys
For me is not the same question as When do you use map vs flatMap in RxJava? because, despite of flatMap being the thing that answer the question, it is not the same question, so anyone who has the same problem/question but don't know that flatmap is the answer, will never reach to use flatmap.
One strength of using RxJava is that you can chain as many async operations (method that would return Observable or Single, repository methods in your case) as you want without falling into callback hells. You see in your code that there are nested subscribe blocks. What if you had to chain more async network operations? You fall into callback hells and the code will become harder to follow and maintain.
Removing nested callbacks and making code more functional, compositional, and readable is one thing RxJava is really good at. In the intro part of ReactiveX website , they mention about this in the intro part of ReactiveX website (http://reactivex.io/intro.html).
Callbacks solve the problem of premature blocking on Future.get() by
not allowing anything to block. They are naturally efficient because
they execute when the response is ready.
But as with Futures, while callbacks are easy to use with a single
level of asynchronous execution, with nested composition they become
unwieldy.
Flatmap operator is to the rescue here. You can look into the definition of flatMap operator in the link below.
http://reactivex.io/documentation/operators/flatmap.html
Below is the code I would use in your case.
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.map { user -> user.name = "another name"; return user; }
.flatMap { user -> userRepository.update(user) }
.doOnSuccess { /* handle response here */ } // doOnNext if you are using observable
.subscribe({ /* or handle response here */ }, { /* must handle error here */})
Flatmap operator flattens Single of update response which will be returned by your repository's update method and pass just the response downstream. Above code is not only easier to read but also makes your code reusable because update logic is now part of the chain.
Distinguishing between map and flatMap is really important in exploiting the full benefit of RxJava so it will be really beneficial to get used to it!
fun remove(data: String): Single<JSONApiObject> {
return service.remove(data)
.onErrorResumeNext(ErrorHandler(ErrorParser()))
}
Is onErrorResumeNext necessary, if I don't intend to do anything onError? This is a POST request.
No. But it recommendable to implement the onError or onErrorResumeNext in order to handle whenever your subscription goes wrong. Otherwise, your program will crash.
For example, in your case, if your POST request fails you can make know your user that is caused by a network disconnection, fields missing or if the server is down.
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.
I have the following requirement:
Multiple observers (fragments) need to subscribe to a data source.
Activity will start a network request. Once the request is successful, each observer will receive the result.
I've trying to do it using cache / publish operators, but the issue is when initial request returns an error. At this point I wish to reset the stream and subsequent calls to the method should run a new network request instead of returning an error each time.
Here's what I have currently.
private Flowable<List<Data>> dataObservable;
private Flowable<List<Data>> getData(){
if(dataObservable == null){
dataObservable = apiService.getData()
.doOnError(throwable -> {
dataObservable = null;
})
.cache();
}
return dataObservable;
}
This works, but the code feels wrong. There's got to be a better way.
You can define the observable ahead of time, and it won't actually do anything until something subscribes to it. That's one less null value to worry about.
You can use the retry() operator, or a variant of it, to automatically retry the network operation on an error.
Finally, the cache() operator will ensure that only one network connection subscription is active. Each subscriber will get any updates from the observable, and will be oblivious to any network errors experienced.
Flowable<List<Data>> dataObservable = apiService.getData()
.retry()
.cache();
Apparently there is no operator for that.
The issue was discussed at Observable, retry on error and cache only if completed
and Plato created a nice tiny lib for that
platoblm/rx-onerror-retry-cache.
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.