<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=2258763691017137&amp;ev=PageView&amp;noscript=1">
Simplex Software

Is onError the only thing we need to know when managing errors in RxJava?
For very basic usages of RxJava probably is all you need, but it doesn't help much when your business logic is created with Rx.

So how do we handle errors, and control the flow of data when exceptions are thrown by observables in flapMap or combineLatest?

## onErrorReturnItem / onErrorReturnNext

These are two of the simplest operators to handle errors, in onErrorReturnItem you return a value that's going to be passed downstream instead of the current error.

on_error_return_item_example
Observable.just("33A")
	.map { it.toInt() } //throws a NumberFormatException
	.onErrorReturnItem(-1) // It doesn't matter which exception it encounters, it will return "-1"
	.subscribe {
		println("Got item $it") // prints Got Item -1
    }

This is particularly useful when no matter what error you may encounter, you always have a default value to return that means something. For example, if you are fetching a List of items from a repository and an error gets thrown, you know that you can safely return an emptyList and keep your program working.

Another case is when you can substitute the original observable with a secondary one that can provide useful information.

on_error_return_next
  fetchItemFromRemoteDB() // This call can fail, because of no internet connection
  	.onErrorReturnNext { fetchItemFromLocalDB() } // If no internet get cached items from local db.
  	.subscribe { }

  

Something important when handling errors in Rx, is that when an Observable gets an onError message it will shut down and close, it will not emit again. This is because a call to onError is a terminal message. So we need to take special care when placing error handling operators to swallow errors in the appropriate observables.

on_error_return_next_example_wrong
streamFromUIButtonClicks // this is an open stream that will receive events while the view is active
	.flatMap { fetchItemFromRemoteDB() } // fetchItemFromRemoteDB throws an exception if no internet.
	.onErrorReturnNext { fetchItemFromLocalDB() } // onErrorReturnNext will catch the error and swap the error with a new value 
	.subscribe { }

The problem with this code is that when we get an error in fetchItemFromRemoteDB it is not immediately handled and then it is seen by the parent stream. As soon as the parent stream gets the onError message it will shut down, so any following clicks will not trigger a fetch to the remote database. The correct way of handling errors would be like this:


on_error_return_next_example_right
streamFromUIButtonClicks // this is an open stream that will receive events while the view is active
	.flatMap { fetchItemFromRemoteDB()
		.onErrorReturnNext { fetchItemFromLocalDB() } 
	}.subscribe { }

By handling the error in the flatMapped observable, the error message is not seen by the parentObservable and thus it doesn't break, and clicks will continue working as expected.

Another useful operator is retry() as the name suggests it will execute again the observable source if it encounters an error.

This operator is a bit tricky, as it has some side effects that you need to take into account before using it. Retry basically resubscribes itself when it encounters an error, so if the observable source executes an expensive operation that you don't want to execute several times, it's better to not use this operator, or use it in a flat map to limit the scope of the retried observable.

Also, if the original observable emits some items and then fails, when retried it will emit the same items again, eg. If original observable emits ["first", "second", Exception], then when retried, the observable will emit ["first" , "second", "third", Complete], then the complete sequence of emissions would be ["first", "second", "first", "second", "third", Complete]. http://reactivex.io/documentation/operators/retry.html

Common uses of retry limit the number of times an operation can be retried before it finally fails, eg.


streamFromUIButtonClicks 
	.flatMap { fetchItemFromRemoteDB()
    	.retry(3) //retry previous operation up to 3 times
        .onErrorReturnNext { fetchItemFromLocalDB() } 
     }
     .subscribe { }

There is another variation of retry, one that accepts a predicate where you can decide if you want to retry the operation given the failure Throwable, and the more complex retryWhen which lets you handle the retry, controlled by the emissions of the returned observable.

This an example of retryWhen from Rx Java docs, it retries with an incremental delay.


Single.timer(1, TimeUnit.SECONDS) 
	.map { throw RuntimeException() }
    	.retryWhen { e ->
        	val counter = AtomicInteger() // Used to count the number of retries
            e.takeWhile { counter.getAndIncrement() != 3 }
            .flatMap { 
            	Flowable.timer(counter.get().toLong(), TimeUnit.SECONDS) // will resubscribe immediately, then after 1 second, then 2 seconds.
            } 
        }
        .subscribe { }

 

Follow Us

Subscribe To Our Blog

Let Us Know What You Thought about this Post.

Put your Comment Below.