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());
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.
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?
I am very new to RxAndroid and am still trying to navigate my way out of the errors that I am making.
Observable.just(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
});
I have a heavy computation method here that I am trying to run on RxJava's Schedulers.computation() thread.(I do not know if only calling it in Observable.just is the right way). The method is supposed to throw an exception if it does not have data.
Class RandomComputeManager{
public static getPieChartData(int a,String b,Date c) throws CustomException {
if(haveData){
//All Okay
}
else{
throw new CustomException("No Data");
}
}
The build is failing with error
error: unreported exception CustomException; must be caught or declared to be thrown
I have tried adding an observer to the subscribe method thinking that it has a onError method but neither is that solving this issue nor am I able to fetch my data then due to some ambiguity in the return value of the called method(Don't know if it should be an observable or just the object I need).
Please suggest a way to handle this.
The subscriber function can take another argument of throwable.
Please do like this
Observable.just(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
}, throwable ->{
});
Observable.fromCallable(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
}, Throwable :: printStackTrace);
This did the trick. Thanks to Gautam Kumar and Raj Suvariya for your help. I found this detail from Exception handling in rxjava
just method accepts parameters that are resolved immediately, so you actually are about to run the computation at the very same line you create your Observable. Also, this is the reason that your exception is not caught, as the getChartData is not called within Observable on-subscribe function.
What you need here is to create the Observable passing a computation function, but you are trying to pass the computed result.
I am used to Kotlin, so sorry if I mess up Java lambdas here, but you should use fromCallable here, like so
Observable.fromCallable(
() -> RandomComputeManager.getChartData(0, "abcd", new Date())))
fromCallable accepts a function that will execute once you subscribe and will emit the function result.
Also, for your purpose it's better to use a Single, as you will have only one item emitted.
Single.fromCallable(
() -> RandomComputeManager.getChartData(0, "abcd", new Date())))
Also if your your CustomException is checked and you don't want to crash, you do have to add onError handling, like already suggested by others.
I have a number of Observables that are used for network requests in my app. Since so much is the same, I apply an Observable transformation to them:
/**
* Creates a transformer that applies the schedulers and error handling for all of the observables in this ViewModel.
*/
private fun applyTransformations(): Observable.Transformer<NetworkState, NetworkState> {
return Observable.Transformer { observable ->
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { NetworkState.Error(it) }
.doOnNext { publishState(it) }
.startWith(NetworkState.Loading())
}
}
The goals I am trying to accomplish with the above:
Apply consistent schedulers
Handle any error by returning an instance of my sealed class.
Handle any onNext by publishing the state returned by the observable.
Start off by sending a Loading state.
This works mostly fine, but what I've noticed is that while I call startWith and a loading state, it is never actually handled by doOnNext(). In other words, publishState() is never called for my loading state.
Where I set up the observables, I don't bother to add a subscriber, because the doOnNext() above is all that I'll need:
val subscription = repository.getInstagramPhotos(count)
.map { mapIGPhotoResponse(it) }
.compose(applyTransformations())
.subscribe()
If I were to supply a subscriber above, though, it would handle the loading state. It would also handle two onNext() calls - one for the subscriber supplied, and one for the doOnNext in the transform.
Is there a way to modify this startWith call to emit to whatever I've specified in doOnNext? I'm using RxJava 1.
Edit: Just to clarify some more, if I track what's emitted I expect to see two things. Loading -> Success. What I actually see is just Success. If I supply a subscriber to the observable I see Loading -> Success -> Success.
startWith should be before doOnNext.
Rxjava methods, though they look like they use the builder pattern, actually don't. They return a new observable each time an operator is applied. In your case, your doOnNext observable completes before your start with observable, so it's consumer isn't called with what you supply in startWith.
Ideally, you should go with:
observable
.startWith(NetworkState.Loading())
.doOnNext { publishState(it) }
.onErrorReturn { NetworkState.Error(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
Also, be careful with subscribing with no Consumer for onEror should it happen. Since you have nothing to consume the onError, RxJava will crash your app since it has nothing to notify for the error. Consider replacing the doOnNext with a Success Consumer in subscribe, and an empty Consumer for the error if you want to ignore it.
Also doOnNext is typically used for side effects, such as logging and the sort, they're more of a convenience than true functional operators.
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()