Standard Error Handler in RxJava2 or why RxJava causes the application to crash even if onError is implemented
- Transfer
The translation of the article will be discussed
We are all good developers and we catch mistakes in
No, not right.
Below we look at a couple of examples in which the application will fall due to
Basic error handler in
In the role of the basic error handler in is
If RxJava does not have a basic error handler, such errors will be hidden from us and the developers will be in the dark about potential problems in the code.
Starting with the version
One of the major mistakes that newcomers face is
Since errors relating to the "bugs" are easy to fix - we will not dwell on them. The errors that it
The cases in which this can occur depend on what the sources and subscribers specifically do. Examples are discussed below, but in general we can say that such an error occurs if there is no active subscriber to whom an error can be delivered.
The first option in which you can call
We combine together two sources, each of which causes an error. What do we expect? We can assume that it
It turns out that with a single call, a
An easy way to get into a situation like this is to use
Here is a rather innocent example that can cause such an error:
If an
This example seems fictional, but it is very easy to get into a situation where
So what to do with these errors?
The first step is to look at the errors that occur and try to determine what causes them. Ideally, if you manage to fix the problem at its source, to prevent the transmission of errors in
The solution to the example with
The example with the
Another way to solve the problem is to replace the basic error handler with your own. Method
If you manually create your own
The moral of this article is that you cannot be sure that there are no errors when working with RxJava if you simply implemented
UndeliverableException
in the RxJava2
version 2.0.6
and newer. If someone ran into and can not figure out, or did not hear at all about this problem - I ask under the cat. Encouraged to transfer the problem in production
after the transition from RxJava1
to RxJava2
. The original was written on December 28, 2017, but it’s better to learn late than never.
onError
when we use RxJava
. This means that we protected ourselves from the application crashes, right? No, not right.
Below we look at a couple of examples in which the application will fall due to
RxJava
, even if correctly implemented onError
.Basic error handler in RxJava
In the role of the basic error handler in is
RxJava
used RxJavaPlugins.onError
. It handles all errors that cannot be delivered to the subscriber. By default, all errors are sent exactly to it, so application crashes may occur. В примечаниях к релизу 2.0.6
This behavior is described:One of the goals of design 2.x is the lack of lost mistakes. Sometimes the sequence ends or is canceled before the source callsonError
. In this case, the error has nowhere to go and it goes toRxJavaPlugins.onError
If RxJava does not have a basic error handler, such errors will be hidden from us and the developers will be in the dark about potential problems in the code.
Starting with the version
2.0.6
, it RxJavaPlugins.onError
tries to be smarter and shares the library / implementation errors and situations when the error cannot be delivered. Errors classified as “bugs” are called as they are, the rest turn into UndeliverableException
and after are caused. All this logic can be seen here (methods onError
and isBug
). One of the major mistakes that newcomers face is
RxJava
- OnErrorNotImplementedException
. This error occurs if it observable
causes an error, and the method is not implemented in the subscriber onError
. This error is an example of an error for a basic error handler.RxJava
is a "bug" and does not turn into UndeliverableException
.UndeliverableException
Since errors relating to the "bugs" are easy to fix - we will not dwell on them. The errors that it
RxJava
wraps in UndeliverableException
are more interesting, since it may not always be obvious why the error cannot be delivered before onError
. The cases in which this can occur depend on what the sources and subscribers specifically do. Examples are discussed below, but in general we can say that such an error occurs if there is no active subscriber to whom an error can be delivered.
Example with zipWith ()
The first option in which you can call
UndeliverableException
- the operator zipWith
.val observable1 = Observable.error<Int>(Exception())
val observable2 = Observable.error<Int>(Exception())
val zipper = BiFunction<Int, Int, String> { one, two -> "$one - $two" }
observable1.zipWith(observable2, zipper)
.subscribe(
{ System.out.println(it) },
{ it.printStackTrace() }
)
We combine together two sources, each of which causes an error. What do we expect? We can assume that it
onError
will be called twice, but this contradicts the specificationReactive streams
.After a single call to a terminal event (onError
,onCompelete
), it is required that no calls be made any more.
It turns out that with a single call, a
onError
second call is no longer possible. What happens when a second error occurs at the source? She will be delivered to RxJavaPlugins.onError
. An easy way to get into a situation like this is to use
zip
network calls to join (for example, two calls Retrofit
that return Observable
). If an error occurs in both calls (for example, there is no Internet connection), both sources will cause errors, the first of which will go to the implementation onError
, and the second will be delivered to the basic error handler ( RxJavaPlugins.onError
).Example with ConnectableObservable without subscribers
ConnectableObservable
can also cause UndeliverableException
. It is worth recalling that ConnectableObservable
causes events, regardless of the presence of active subscribers, just call the method connect()
. If in the absence of subscribers ConnectableObservable
an error occurs, it will be delivered to the basic error handler. Here is a rather innocent example that can cause such an error:
someApi.retrofitCall() // Сетевой вызов с использованием Retrofit
.publish()
.connect()
If an
someApi.retrofitCall()
error occurs (for example, there is no connection to the Internet), the application will crash, as the network error will be delivered to the basic error handler RxJava
. This example seems fictional, but it is very easy to get into a situation where
ConnectableObservable
everything is still connected (connected), but it has no subscribers. I encountered this when used autoConnect()
when calling the API. autoConnect()
does not automatically shut off Observable
. I unsubscribed in the onStop
method Activity
, but the result of the network call was returned after the destruction Activity
and the application fell from UndeliverableException
.We process errors
So what to do with these errors?
The first step is to look at the errors that occur and try to determine what causes them. Ideally, if you manage to fix the problem at its source, to prevent the transmission of errors in
RxJavaPlugins.onError
. The solution to the example with
zipWith
is to take one or both sources and implement one of the methods for catching errors in them . For example, you can use the onErrorReturn
default instead of an error. The example with the
ConnectableObservable
fix is easier - just make sure that you disconnect Observable
at the moment when the last subscriber unsubscribes. autoConnect()
, for example, has an overloaded implementation that takes a function that captures the moment of the connection ( more can be found here). Another way to solve the problem is to replace the basic error handler with your own. Method
RxJavaPlugins.setErrorHandler(Consumer<Throwable>)
will help you with this. If this is the right solution for you, you can intercept all the errors sent to RxJavaPlugins.onError
and process them as you see fit. This solution can be quite complicated - remember that it RxJavaPlugins.onError
receives errors from all streams RxJava
in the application. If you manually create your own
Observable
, you can emitter.onError()
call instead emitter.tryOnError()
. This method sends an error only if the stream (stream) is not destroyed (terminated) and has subscribers. Remember that this method is experimental. The moral of this article is that you cannot be sure that there are no errors when working with RxJava if you simply implemented
onError
in subscribers. You should be aware of situations in which errors may be inaccessible to subscribers, and make sure that these situations are handled.