How to subscribe to click events so exceptions don't unsubscribe? - android

I'm using RxJava for Android (RxAndroid) and I subscribe to click events of a view, and do something on them as follows:
subscription = ViewObservable.clicks(view, false)
.map(...)
.subscribe(subscriberA);
The problem is whenever there is an exception, subscriberA automatically unsubscribes, leading to the next click not triggering anything.
How to handle exceptions so to intercept all the click events regardless of exceptions being thrown?

Use retry method:
subscription = ViewObservable.clicks(view, false)
.map(...)
.retry()
.subscribe(subscriberA)
However, you will not receive any exception in onError.
To handle exceptions with retry (resubscribe) logic use retryWhen:
subscription = ViewObservable.clicks(view, false)
.map(...)
.retryWhen(new Func1<Observable<? extends Notification<?>>, Observable<?>>() {
#Override
public Observable<?> call(Notification errorNotification) {
Throwable throwable = errorNotification.getThrowable();
if (errorNotification.isOnError() && handleError(throwable)) {
// return the same observable to resubscribe
return Observable.just(errorNotification);
}
// return unhandled error to handle it in onError and unsubscribe
return Observable.error(throwable);
}
private boolean handleError(Throwable throwable) {
// handle your errors
// return true if error handled to retry, false otherwise
return true;
}
}
.subscribe(subscriberA)

Related

RxJava return error as onNext and continue stream

so i tried to use onErrorReturn to return the result that i wanted but it will complete the stream afterwards, how do i catch the error return as Next and still continue the stream?
with code below, it wont reach retryWhen when there is error and if i flip it around it wont re-subscribe with retryWhen if there is an error
fun process(): Observable<State> {
return publishSubject
.flatMap { intent ->
actAsRepo(intent) // Might return error
.map { State(data = it, error = null) }
}
.onErrorReturn { State(data = "", error = it) } // catch the error
.retryWhen { errorObs ->
errorObs.flatMap {
Observable.just(State.defaultState()) // continue subscribing
}
}
}
private fun actAsRepo(string: String): Observable<String> {
if (string.contains('A')) {
throw IllegalArgumentException("Contains A")
} else {
return Observable.just("Wrapped from repo: $string")
}
}
subscriber will be
viewModel.process().subscribe(this::render)
onError is a terminal operator. If an onError happens, it will be passed along from operator to operator. You could use an onError-operator which catches the onError and provides a fallback.
In your example the onError happens in the inner-stream of the flatMap. The onError will be propagated downstream to the onErrorReturn opreator. If you look at the implementation, you will see that the onErrorReturn lambda will be invoked, the result will be pushed downstream with onNext following a onComplete
#Override
public void onError(Throwable t) {
T v;
try {
v = valueSupplier.apply(t);
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
downstream.onError(new CompositeException(t, e));
return;
}
if (v == null) {
NullPointerException e = new NullPointerException("The supplied value is null");
e.initCause(t);
downstream.onError(e);
return;
}
downstream.onNext(v); // <--------
downstream.onComplete(); // <--------
}
What is the result of your solution?
Your stream completes because of: #retryWhen JavaDoc
If the upstream to the operator is asynchronous, signalling onNext followed by onComplete immediately may result in the sequence to be completed immediately. Similarly, if this inner {#code ObservableSource} signals {#code onError} or {#code onComplete} while the upstream is active, the sequence is terminated with the same signal immediately.
What you ought to do:
Place the onErrorReturn behind the map opreator in the flatMap. With this ordering your stream will not complete, when the inner-flatMap stream onErrors.
Why is this?
The flatMap operator completes, when the outer (source: publishSubject) and the inner stream (subscription) both complete. In this case the outer stream (publishSubject) emits onNext and the inner-stream will complete after sending { State(data = "", error = it) } via onNext. Therefore the stream will remain open.
interface ApiCall {
fun call(s: String): Observable<String>
}
class ApiCallImpl : ApiCall {
override fun call(s: String): Observable<String> {
// important: warp call into observable, that the exception is caught and emitted as onError downstream
return Observable.fromCallable {
if (s.contains('A')) {
throw IllegalArgumentException("Contains A")
} else {
s
}
}
}
}
data class State(val data: String, val err: Throwable? = null)
apiCallImpl.call will return an lazy observable, which will throw an error on subscription, not at observable assembly time.
// no need for retryWhen here, except you want to catch onComplete from the publishSubject, but once the publishSubject completes no re-subscription will help you, because the publish-subject is terminated and onNext invocations will not be accepted anymore (see implementation).
fun process(): Observable<State> {
return publishSubject
.flatMap { intent ->
apiCallImpl.call(intent) // Might return error
.map { State(data = it, err = null) }
.onErrorReturn { State("", err = it) }
}
}
Test
lateinit var publishSubject: PublishSubject<String>
lateinit var apiCallImpl: ApiCallImpl
#Before
fun init() {
publishSubject = PublishSubject.create()
apiCallImpl = ApiCallImpl()
}
#Test
fun myTest() {
val test = process().test()
publishSubject.onNext("test")
publishSubject.onNext("A")
publishSubject.onNext("test2")
test.assertNotComplete()
.assertNoErrors()
.assertValueCount(3)
.assertValueAt(0) {
assertThat(it).isEqualTo(State("test", null))
true
}
.assertValueAt(1) {
assertThat(it.data).isEmpty()
assertThat(it.err).isExactlyInstanceOf(IllegalArgumentException::class.java)
true
}
.assertValueAt(2) {
assertThat(it).isEqualTo(State("test2", null))
true
}
}
Alternative
This alternative behaves a little bit different, than the first solution. The flatMap-Operator takes a boolean (delayError), which will result in swallowing onError messages, until the sources completes. When the source completes, the errors will be emitted.
You may use delayError true, when the exception is of no use and must not be logged at the time of appearance
process
fun process(): Observable<State> {
return publishSubject
.flatMap({ intent ->
apiCallImpl.call(intent)
.map { State(data = it, err = null) }
}, true)
}
Test
Only two values are emitted. The error will not be transformed to a fallback value.
#Test
fun myTest() {
val test = process().test()
publishSubject.onNext("test")
publishSubject.onNext("A")
publishSubject.onNext("test2")
test.assertNotComplete()
.assertNoErrors()
.assertValueAt(0) {
assertThat(it).isEqualTo(State("test", null))
true
}
.assertValueAt(1) {
assertThat(it).isEqualTo(State("test2", null))
true
}
.assertValueCount(2)
}
NOTE: I think you want to use switchMap in this case, instead of flatMap.

RxJava RetryWhen, This processor allows only a single Subscriber

I am learning how to do data polling in RxJava2
Here is my code so far.
private io.reactivex.Single<String> getMyTask() {
return io.reactivex.Single.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
Log.d("ERSEN","Task Started!");
Random random = new Random(System.currentTimeMillis());
if(random.nextBoolean()){
return "WORK COMPLETED";
}
Log.d("ERSEN","Task Had An Error!");
throw new IllegalArgumentException();
}
});
}
The above is my Single which emits a String basically simulating some work.
I also make the task randomly succeed and fail to the test the case when a poll event fails to check if re-subscription occurs correctly
My problem
compositeDisposable.add(getMyTask()
.repeatWhen(new Function<Flowable<Object>, Publisher<?>>() {
#Override
public Publisher<?> apply(final Flowable<Object> objectFlowable) throws Exception {
return objectFlowable.delay(INTERVAL, TimeUnit.SECONDS);
}
})
.retryWhen(throwableFlowable -> throwableFlowable.flatMap(new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof ClassCastException) {
return Flowable.error(throwable);
}
return throwableFlowable.delay(INTERVAL, TimeUnit.SECONDS);
}
}))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError));
In the above, I am resubscribing to the Observable when it emitted some data successfully.
I am having problems with retryWhen.
For this example I wish to not retry if a ClassCastException occurs.
In my Observable this is not produced which is for a reason because I am testing the logic to retry only on certain errors
However, I am reviving this error with the above code when an error in the Observable is produced
This processor allows only a single Subscriber
I am not sure what is wrong, I have been following this blog post
http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/
Thanks for reading
Let me know if you would like me to post any more details
You are resubscribing to the error flow in your retryWhen which is not allowed and doesn't make sense in your situation. You should delay a value in flatMap instead:
.retryWhen(throwableFlowable -> throwableFlowable.flatMap(
new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof ClassCastException) {
return Flowable.error(throwable);
}
return Flowable.just("ignored").delay(INTERVAL, TimeUnit.SECONDS);
}
}
))

RxJava2's UndeliverableException and disposed streams

I feel like I'm not understanding something about RxJava2's error-handling. (btw, I have read RxJava2 observable take throws UndeliverableException and I understand what happens, but not exactly why or if there's a better way to deal with it than what I discuss below. I have also seen RuntimeException thrown and not caught in RxJava2 Single after it has been disposed and it doesn't seem exactly relevant.)
Let's say I have the following, very much simplified example:
val sub = Observable.fromCallable(callableThatCanReturnNull)
.subscribe({ println(it) }, { System.err.println(it) })
And the following sequence of events happens, in order:
sub.dispose()
That darn Callable returns null.
Looking at the RxJava2 source, I see this:
#Override
public void subscribeActual(Observer<? super T> s) {
DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(s);
s.onSubscribe(d);
if (d.isDisposed()) {
return;
}
T value;
try {
value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null");
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
if (!d.isDisposed()) {
s.onError(e);
} else {
RxJavaPlugins.onError(e);
}
return;
}
d.complete(value);
}
Apparently sometime between the first d.isDisposed() check and the second, d is disposed, and so we hit RxJavaPlugins.onError(e) with e being a NullPointerException (from ObjectHelper.requireNonNull()). If we have not called RxJavaPlugins.setErrorHandler() with something, then the default behavior is to throw a fatal exception that will crash the application. This seems Bad (tm). My current approach is the following:
GlobalErrorFilter errorFilter = new GlobalErrorFilter(BuildConfig.DEBUG /* alwaysCrash */);
RxJavaPlugins.setErrorHandler(t -> {
if (errorFilter.filter(t)) {
// Crash in some cases
throw new RuntimeException(t);
} else {
// Just log it in others
Logger.e("RxError", t, "Ignoring uncaught Rx exception: %s", t.getLocalizedMessage());
}
});
And my GlobalErrorFilter takes into consideration this fromCallable case and also UndeliverableException. In fact, it looks like this:
class GlobalErrorFilter(private val alwaysCrash: Boolean = false) {
/**
* Returns `true` if app should crash; `false` otherwise. Prefers to return `true`.
*/
fun filter(t: Throwable) = when {
alwaysCrash -> true
t is UndeliverableException -> false
t.localizedMessage.contains("Callable returned null") -> false
else -> true
}
}
This feels really hackish. What I want is to tell RxJava that if I have disposed of an observable, I do not care whether (1) it emits new items (2) it completes (3) it onErrors. I just don't care. Is there a way to do that without this global error handler?

Retrofit2 + RxJava error handling

I am using RxJava and Retrofit2 (with OkHttp as the HTTP client) to do networking and am trying to understand how different errors are handled by Retrofit2 and how they look from the RxJava side. The following code illustrates an RxJava Subscriber callback for a network call (made with Retrofit).
Subscription subscription = observable
.subscribeOn(mScheduler)
.observeOn(mAndroidScheduler)
.subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
Timber.d("onCompleted called");
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}
#Override
public void onError(Throwable e) {
Timber.d("onError called");
Timber.d(e.toString());
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}
#Override
public void onNext(User user) {
Timber.d("onNext called");
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
mActivityView.launchMainActivity();
}
});
My question is, in what cases will onError() be called and once it's been called, how can I interrogate the Throwable to determine the cause?
From the Retrofit source it looks like the only Throwables that are possible to see are IOException and HttpException. Can anyone verify that that is true?
Here's the basics: onError() will be called if:
the observable you're subscribing to throws an exception (e.g. you get an IOException while trying to read a file)
an exception is raised in your onNext() method.
If there's an exception in your onComplete(), RxJava will propagate an rx.exceptions.OnCompletedFailedException and if there's an exception in onError() - you'll get rx.exceptions.OnErrorFailedException.
That said, you can just probe the Throwable you receive in your onError() method for exceptions that you're expecting. For example you know that if your API call results in client error (4xx), Retrofit will wrap it into HttpException. If there's a timeout with the request you'll get a SocketTimeoutException. Here's a rough example:
#Override
public void onError(Throwable e) {
Timber.d("onError called");
Timber.d(e.toString());
handleError(e);
}
private handleError(Throwable throwable) {
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException)throwable;
int statusCode = httpException.code();
// handle different HTTP error codes here (4xx)
} else if (throwable instanceof SocketTimeoutException) {
// handle timeout from Retrofit
} else if (throwable instanceof IOException) {
// file was not found, do something
} else {
// generic error handling
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}
Do not use onError for flow. That'd be as bad as try-catch for flow.
Error HTTP codes, are valid responses and you should not deal with them in onError.
You can wrap the return type of your Retrofit services in Result, that gives you the means to get information about what happen with your call without throwing exceptions.
You can handle the state of your app using this pattern:
service.getSomething()
.map(r -> Model.success(r.response()))
.onErrorReturn(Model::error)
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource.loading())
.subscribe(r -> {
myProgressBar.setVisible(r.isLoading());
if (r.isSuccess()) {
handleSuccess(); // e.g. 400 is also success but needs handling
}
if (r.isError()) {
handleError();
}
}, OnErrorNotImplementedException::new);
See how I tried to handle all possible states within the stream and deliberately I throw OnErrorNotImplementedException for something I might've missed. This is very personal but I prefer to crash-fast-and-furious rather than being in an unknown state for a while that later will manifest in a crash harder to debug.
In Kotlin I have used bellow like..
disposable.add(apiService.getLogin_service(parment1,parment1)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<Login_Reg_Data_Model>() {
override fun onSuccess(model: Login_Reg_Data_Model) {
//success
}
override fun onError(e: Throwable) {
if (e is HttpException) {
// We had non-200 http error
Log.e("time exceptionr******>",e.message)
} else if (e is SocketTimeoutException) {
//time exception
Log.e("time exception******>",e.message)
} else if (e is IOException) {
// A network error
Log.e("network error******>",e.message)
} else {
//unknown error
Log.e("unknown error******>",e.message)
}
}
})
)

retrofit with rxjava handling network exceptions globally

I am trying to handle exceptions in app on global level, so that retrofit throws an error i catch it in some specific class with logic for handling those errors.
I have an interface
#POST("/token")
AuthToken refreshToken(#Field("grant_type") String grantType, #Field("refresh_token") String refreshToken);
and observables
/**
* Refreshes auth token
*
* #param refreshToken
* #return
*/
public Observable<AuthToken> refreshToken(String refreshToken) {
return Observable.create((Subscriber<? super AuthToken> subscriber) -> {
try {
subscriber.onNext(apiManager.refreshToken(REFRESH_TOKEN, refreshToken));
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}).subscribeOn(Schedulers.io());
}
When i get 401 from server (invalid token or some other network related error) i want to refresh the token and repeat the rest call. Is there a way to do this with rxjava for all rest calls with some kind of observable that will catch this error globally, handle it and repeat the call that throw-ed it?
For now i am using subject to catch the error on .subscribe() like this
private static BehaviorSubject errorEvent = BehaviorSubject.create();
public static BehaviorSubject<RetrofitError> getErrorEvent() {
return errorEvent;
}
and in some call
getCurrentUser = userApi.getCurrentUser().observeOn(AndroidSchedulers.mainThread())
.subscribe(
(user) -> {
this.user = user;
},
errorEvent::onNext
);
then in my main activity i subscribe to that behaviour subject and parse the error
SomeApi.getErrorEvent().subscribe(
(e) -> {
//parse the error
}
);
but i cant repeat the call for the observable that throw the error.
You need to use the operator onErrorResumeNext(Func1 resumeFunction), better explained in the official wiki:
The onErrorResumeNext( ) method returns an Observable that mirrors the behavior of the source Observable, unless that Observable invokes onError( ) in which case, rather than propagating that error to the Subscriber, onErrorResumeNext( ) will instead begin mirroring a second, backup Observable
In your case I would put something like this:
getCurrentUser = userApi.getCurrentUser()
.onErrorResumeNext(refreshTokenAndRetry(userApi.getCurrentUser()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)
where:
private <T> Func1<Throwable,? extends Observable<? extends T>> refreshTokenAndRetry(final Observable<T> toBeResumed) {
return new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
// Here check if the error thrown really is a 401
if (isHttp401Error(throwable)) {
return refreshToken().flatMap(new Func1<AuthToken, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(AuthToken token) {
return toBeResumed;
}
});
}
// re-throw this error because it's not recoverable from here
return Observable.error(throwable);
}
};
}
Note also that this function can be easily used in other cases, because it's not typed with the actual values emitted by the resumed Observable.
#Override
public Observable<List<MessageEntity>> messages(String accountId, int messageType) {
return mMessageService.getLikeMessages(messageType)
.onErrorResumeNext(mTokenTrick.
refreshTokenAndRetry(mMessageService.getLikeMessages(messageType)));
}

Categories

Resources