I am using RxJava2 to handle downloading some data using Retrofit2, saving it to the database, and showing an error message with an option to retry if something fails. If that is successful, I need to make another network request, handle the response, and present a different error message if that one failed. Once everything is finished, the user can continue on to the next screen.
I have had some success, but seem to not be able to get everything working at the same time. Either some of my tasks get skipped and the user jumps to the end of the chain, or the observables do not run sequentially.
Here is my latest attempt.
Disposable disposable = initializeMySdk()
.subscribeOn(Schedulers.computation())
.andThen(checkStatus())
.observeOn(AndroidSchedulers.mainThread())
.map(userStatus -> downloadAppData())
.flatMapCompletable((observable) ->
observable.flatMapCompletable((b) -> downloadUserData(getUserId())))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
startNextActivity();
}, throwable -> Log.e(TAG, throwable.getMessage()));
initializeMySdk() returns a Completable.
checkStatus() returns Single.
downloadAppData() looks like this:
private Observable<Object> downloadAppData() {
return ApiUtils.downloadAppData()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
mNoConnectionLayout.setVisibility(View.VISIBLE);
})
.retryWhen(throwableObservable -> RxView.clicks(mRetryBtn))
.flatMap(stringResponse -> handleNetworkResponse(stringResponse))
.observeOn(AndroidSchedulers.mainThread());
}
downloadUserData is similar to downloadAppData, but instead of having a dialog with a "Retry" button, it is just a dialog that will let you continue. The is how it is working now, downloadUserData isn't getting called. Something seems funny with my flatMapCompletables.
Any help would be greatly appreciated!
UPDATE
Here is what my block of code looks like now:
Disposable disposable = initializeMySdk()
.subscribeOn(Schedulers.computation())
.andThen(checkStatus())
.flatMapCompletable(userStatus -> downloadAppData())
.andThen(downloadUserData(getUserId()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
startNextActivity();
}, throwable -> Log.e(TAG, throwable.getMessage()));
Now the problem seems to be in my retryWhen line:
.retryWhen(throwableObservable -> RxView.clicks(mRetryBtn))
I am getting this error message:
Expected to be called on the main thread but was RxComputationThreadPool-1
I have tried adding .observeOn(AndroidSchedulers.mainThread()) in multiple places throughout my code without any luck. Removing the retryWhen seems to fix my problems, but I need the ability to retry. ApiUtils.downloadAppData() is making a network request using Retrofit2, so that cannot run on the main thread.
If ApiUtils.downloadAppData() throws as error, I show an error screen to the user that has a Retry button, and that is the button that calls retryWhen. Why am I not able to run ApiUtils.downloadAppData() on the background thread, show my error screen and observe the retryWhen on the main thread, then retry ApiUtils.downloadAppData() on the background thread, and able to try this as many times as needed?
Related
I am trying to call multiple network calls(5 in total) ,each is independent of the others. I know how to call network calls in chain with RxJava. But by calling in chain, if there is an error in calling 3rd or 4th network call, the rest of the calls will not be executed. So I want the rest of the network calls to continue to be executed even when the former calls failed. Is there a way to achieve the solution for this situation?
Yes, there is, you can use onErrorResumeNext. example of my code :
primaryMenuFetcher.getMenu()
.observeOn(uiScheduler)
.flatMap { menuItems ->
onView {
primaryMenu = menuItems
setPrimaryMenuList(primaryMenu)
}
return#flatMap model.getPromotions()
}
.onErrorResumeNext { return#onErrorResumeNext model.getPromotions() }
.observeOn(uiScheduler)
.doFinally { onView { hideProgressBar() } }
.subscribe({ fetchedLeagues ->
onView {
featuredLeagues = fetchedLeagues
showPopularLeagues()
setPopularLeaguesList(featuredLeagues)
}
}, {
showError()
})
There is also other Rx2 error handling options. Refer documentation
I think one of the best things here would be mergeDelayError. This will delay the errors until all observables completed or errored.
This would be one option:
Observable.mergeDelayError(
obs1.subscribeOn(Schedulers.io()),
obs2.subscribeOn(Schedulers.io()),
//...
)
.subscribe();
The subscribeOn is more to guarantee that the requests run in parallel. I don't know if the io scheduler is the most suitable in this scenario, but it has been working for me.
If the stream errors, you'll get a composite exception with each problem that occurred for each individual observable. If you need to check these individually you can look at this exception.
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()
AIM: Refresh an adapter when something changes, but only when the user has stopped scrolling the list.
I am trying to throw an error in doOnNext so that I can then wait and retry again. However, this isn't working. I can see that the exception is being thrown, but retryWhen doesn't seem to be doing anything.
What am I doing wrong here?
someObservable()
.observeOn(AndroidSchedulers.mainThread())
.debounce(650, TimeUnit.MILLISECONDS)
.doOnNext(o -> {
if (isListScrolling()) {
throw new IllegalStateException();
}
})
.retryWhen(errors -> errors.flatMap(error -> Observable.timer(100, TimeUnit.MILLISECONDS)))
.subscribe(o -> refreshListNow());
UPDATE:
someObservable is a Subject (Bus) that I'm observing UI events on.
See my comment below, as it seems retry() here won't work how one would expect (i.e. retry does not cause the source to automatically re-emit the last value, unless I was perhaps to use a BehaviorSubject - which isn't suitable here).
You didn't mention what value is emmited. So I'll just make it a string for this example. Checking the scrolling state inside doOnNext is too late, since they can not be propagated back to retryWhen. You can simply use flatMap to throw an error instead.
Observable.just("someValue")
.observeOn(AndroidSchedulers.mainThread())
.debounce(650, TimeUnit.MILLISECONDS)
.flatMap((Func1<String, Observable<?>>) s -> {
if (isListScrolling()) {
// Still scrolling, pass error so we can retry in the next step
return Observable.error(new Exception());
}
// Not scrolling, we can continue
return Observable.just("someValue");
})
.retry()
.subscribe(o -> refreshListNow());
I am new with RxAndroid, I would like to understand why button clicks event stop working. I am using RxBinding and Retrofit 2 with Observables.
Subscription loginButtonSubscription = RxView.clicks(loginBtn)
.throttleFirst(Constants.CLICK_THRESHOLD_MILLIS, TimeUnit.MILLISECONDS)
.flatMap(aVoid -> authApi.login(new LoginUserRequest(emailEditText.getText().toString(), passwordEditText.getText().toString())))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(verifyEmailResponse -> Log.i("TEST", "onNext: " + verifyEmailResponse.success),
throwable -> handleError(throwable));
If I got an error (from login retrofit service) the method handleError is executed fine, but after that the click event is not fired anymore.
I am doing something wrong but I cant understand. There is better way to handle retrofit error?
Thanks
Any error in an RxJava chain will cause both an onError and an onCompleted event, this is by design. Once the onCompleted event is called you are no longer subscribed hence the behaviour you are seeing.
In your case you want to keep the button working even if Retrofit returns an error which means it's worth you looking into the retry() operator from RxJava.
Example but not guaranteed what you need:
Subscription loginButtonSubscription = RxView.clicks(loginBtn)
.throttleFirst(Constants.CLICK_THRESHOLD_MILLIS, TimeUnit.MILLISECONDS)
.flatMap(aVoid -> authApi.login(new LoginUserRequest(emailEditText.getText().toString(), passwordEditText.getText().toString())))
.doOnError(this::handleLoginError)
.retry()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
verifyEmailResponse -> Log.i("TEST", "onNext: " + verifyEmailResponse.success),
throwable -> handleError(throwable));