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.
Related
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.
When I make a request to the network, if an error occurs, then I will return data from the cache and the error. But sometimes I don’t get data from the cache, but I get only an error. The first time I launch the application, I always get only an error. If I call the getDashboard method once or several times, then everything is fine.
Here is a piece of code.
.onErrorResumeNext(throwable -> {
return Observable.concat(Observable.just(fromCache), Observable.error(throwable));
});
Full code here
https://gist.github.com/githubgist123/7e027675bb4db07fef606e23f39f8a96
Sorry, but the way you're doing your caching is wrong in my opinion. If you request twice consecutively and the cache is empty you'll end up with two network requests running and racing each other to write to the cache. You don't want that.
What you need is:
Observable.concat(
cache(),
network().share().doOnNext(setCache(...)).onErrorReturn(...)
.first()
)
You might think of synchronized your cache too or make it thread-safe by using the proper data structure.
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.
Currently I am investigating a migration to RxJava and decided that a manager of mine(accountManager) would be an interesting place to start. Currently the Manager has a list of listeners and sends updates accordingly, both when the account gets updated and when something goes wrong.
private List<WeakReference<ProfileChangeListener>> mListeners = new ArrayList<>();
public interface ProfileChangeListener {
void onProfileUpdated(Account account);
void onProfileFailed(Exception e);
}
My Rx solution involves a Subject
private SerializedSubject<Account, Account> mManagerSubject = new SerializedSubject<>(BehaviorSubject.<Account>create());
public Observable<Account> observe() {
return mManagerSubject;
}
and then when an update happens I call one of the following:
private void onProfileUpdated(Account account) {
mManagerSubject.onNext(account);
}
private void onProfileFailed(final Exception e) {
mManagerSubject.onError(e);
}
Issue
The Issue is that once onError is called anyone listening via observe will never get another update from onNext.
I still want the subscribers to receive onError so they can handle the error state but at a later time onNext could still be called with an updated account and I still want the subscribers to handle the updated account.
I've tried solutions using onErrorResumeNext, onErrorReturn onExceptionResumeNext but none of them propagate the onError.
TLDR: How do I keep the subscribers subscribed after onError is called while still propagating onError?
"Errors" in Rx can be a a little difficult to grasp at first, because they have a slightly different meaning from what most people expect.
From the Error Handling documentation (emphasis mine):
An Observable typically does not throw exceptions. Instead it notifies any observers that an unrecoverable error has occurred by terminating the Observable sequence with an onError notification.
onError() is supposed to be used when an Observable encounters an unrecoverable error- that is when your Observable cannot continue emitting items. When you are subscribing, you might use something like onErrorResumeNext to try some recovery action, but that should be the end of the source Observable.
Instead, you may want to adjust what your Observable emits to support emitting an error item, or include a flag indicating that an error was encountered.
If your error truly is unrecoverable, then you may want to revisit your recovery strategy and try a slightly different approach.
I am seeking an example of a flow I'm trying to implement with help of RxJava.
Suppose I want to show a list of data. The flow should look something like this:
Read cache. If it contains the data, show it;
Send an API request to the server:
If it returned the data, then cache it and show it.
If it returned and error and there was no cached data, then show an error.
If it returned and error and there was something cached, then do nothing.
Right now I have a method that does something similar (with lots of inspiration from Jake's u2020). The main difference is that it uses in-memory caching, which means there's no need for a separate Observable for reading from cache and it can be done synchronously.
I don't know how to combine two observables (one for reading from cache and the other for API call) and obtain the flow described above.
Any suggestions?
I think I solved my problem. The observable chain looks like so:
apiCall()
.map(data -> dataInMemory = data)
.onErrorResumeNext(t -> data == null ?
Observable.just(Data.empty()) : Observable.empty())
.startWith(readDataFromCache().map(data -> dataInMemory = data))
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe(dataRequest);
The main point is, that if readDataFromCache() throws an error, it will call onCompleted() without calling onError(). So it should be a custom Observable which you can control.
Data.empty() is a stub for my data - the Subscriber should treat it as an error.
dataInMemory is a member in my controller which acts as in-memory cache.
EDIT: the solution doesn't work properly. The completion of one use case (see comment) is not achieved.
EDIT 2: well, the solution does work properly after some tweaking. The fix was returning different types of observables depending on the state of in-memory cache. Kind of dirty.
Here is my solution:
readDataFromCache().defaultIfEmpty(null)
.flatMap(new Func1<Data, Observable<Data>>() {
#Override
public Observable<Data> call(final Data data) {
if (data == null) {
// no cache, show the data from network or throw an error
return apiCall();
} else {
return Observable.concat(
Observable.just(data),
// something cached, show the data from network or do nothing.
apiCall().onErrorResumeNext(Observable.<Data>empty()));
}
}
});
I don't add the subscribeOn and observeOn because I'm not sure readDataFromCache() should use ioScheduler or uiScheduler.