Open In App

Error Handling in RxJava

Last Updated : 13 Jun, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

While developing an Android App we come across a lot of errors. Even a single line of code can cause an error which can ruin the whole project. There are many different types of Errors and each needs a different way of handling them but in this article, we are specifically going to focus on Error handling in RxJava. So before that let’s understand a few important things.

  • What is an Error?
  • What is RxJava?

What is an Error?

Errors are problems in a program. They can be understood as a mistake or an output that got executed but that output was not expected. Errors can occur due to small mistakes in code. Sometimes what happens is our whole program stops executing because of a small error but we want to handle that error and let our program be executed, that’s why error handling is necessary.

What is RxJava?

RxJava is a library used for reactive functional programming like coroutines. It allows us to do asynchronous tasks and execute event-based programs by using observable sequences. The dependency for RxJava is given below

 implementation ‘io.reactivex.rxjava3:rxandroid:3.0.0’

 implementation ‘io.reactivex.rxjava3:rxjava:3.0.0’

Let’s see an example code of  RxJava successfully executing without errors

In the below code, the onNext function is getting called till it prints all the integers and once it prints all the integers the onComplete function gets called which the program got successfully executed.

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete ", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(t: Int) {
            Log.d("onNext ", t.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError ", e.localizedMessage)
        }
    })


Output: 

/onNext: 1
/onNext: 2
/onNext: 3
/onNext: 4
/onComplete: Completed

Now lets see a program where we are intentionally throwing an exception on a certain condition

In the code below we are throwing a Null pointer Exception when the integer is 3. As soon as we get the exception the onNext function does not get called and the code stopped executing or is terminated.

Kotlin




Observable.just(1,2,3,4).subscribe(object : Observer<Int> {
    override fun onComplete() {
        Log.d("onComplete ", "Completed")
    }
    override fun onSubscribe(d: Disposable) {
    }
    override fun onNext(i: Int) {
        if (i == 3) {
            throw NullPointerException("Its a Null Pointer Exception")
        }
        Log.d("onNext ", i.toString())
    }
    override fun onError(e: Throwable) {
        Log.d("onError ", e.localizedMessage)
    }
})


Output:

Its a Null Pointer Exception

Error Handling in RxJava using different RxJava operators

In RxJava Operators are components which allows you manipulate the data emitted by Observables. Below are the operators in RxJava used for handling errors, We will learn about each of them.

  1. onExceptionResumeNext()
  2. onErrorResumeNext()
  3. doOnError()
  4. onErrorReturnItem()
  5. onErrorReturn()

1. onExceptionResumeNext() 

If this operator gets an exception even before the start of onComplete, onError, and onNext then it will directly go to onExceptionResumeNext and will perform the task inside the scope of onExceptionResumeNext(). Let’s understand this in more detail with the help of the following code.

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnNext {
        if (it == 2) {
            throw (RuntimeException("Exception on 2"))
        }
    }.onExceptionResumeNext {
        Log.d("onExceptionResumeNext : ","1")
    }
    .subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete ", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(i: Int) {
            Log.d("onNext : ", i.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError", e.localizedMessage)
        }
    })


Output: 

onNext : 1
onExceptionResumeNext : 1

When the OnNext() gets called the first time and prints 1 and when the value 2 is returned then onExceptionResumeNext gets called and hence the code for printing 1 inside it gets executed.

2. OnErrorResumeNext()

This operator handles the throwable generated by the program. Let’s take a look at the below code.

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .map { x ->
        if (x == 2) {
            throw NullPointerException("Its a NPE")
        }
        x * 10
    }.onErrorResumeNext { throwable: Throwable ->
        return@onErrorResumeNext ObservableSource {
            Log.d("onErrorResumeNext", throwable.localizedMessage)
        }
    }.subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(i: Int) {
            Log.d("onNext", i.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError", e.localizedMessage)
        }
    })


Output: 

onNext: 10
onErrorResumeNext: Its a NPE

When the onNext gets called and prints the first integer after multiplying it from 10 in the map operator and when the value 2 comes up it throws the NPE Exception. It will not be moving to onError directly and the error would be handled in onErrorResumeNext and we can perform the rest actions in that. 

3. doOnError()

The doOnError operator is somewhat unique from the rest of the operators. We can call it a side-effect operator, In the case of this operator the user will see the error before the exception has occurred.

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnNext {
        if (it == 2) {
            throw (RuntimeException("Exception on 2"))
        }
    }
    .doOnError {
        Log.d("doOnError", "Exception Occurred")
    }.subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(i: Int) {
            Log.d("onNext", i.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError", e.localizedMessage)
        }
    })


Output:

onNext: 1
doOnError: Exception Occurred
onError: Exception on 2

Int the above output, where the first 1 is printed from onNext and then when the value reaches  2, Exception is getting occurred. Since we called a side-effect operator, doOnError gets called first before the onError method. If we did not use a side-effect operator, the OnError operator should be getting called. so in short side-effect operators are those who get called before the main methods like onNext(), onError().

4. onErrorReturnItem()

This operator returns an item whenever an exception or an error is occurred then followed by the onComplete() operator.

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnNext {
        if (it == 2) {
            throw (RuntimeException("Exception on 2"))
        }
    }
    .onErrorReturnItem(10)
    .subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete ", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(i: Int) {
            Log.d("onNext", i.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError", e.localizedMessage)
        }
    })


Output:

onNext: 1
onNext: 10
onComplete: Completed

When onNext() is called and value 1 gets printed and we get an Exception when the value becomes 2 in doOnNext(). But as we are using the onErrorReturnItem() operator, it will return an item when the exception has occurred of the same data type. Here, as we are using the integer data type so it will return an item of integer data type in the onNext() operator, and then it will call the onComplete() operator at last.

5. onErrorReturn()

Sometimes we need to generate a default item whenever an error or exception occurs. Hence we use the onErrorReturn() which provides a lambda and a throwable to return. Let’s see the code below

Kotlin




Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doOnNext {
        if (it == 2) {
            throw (RuntimeException("Exception on 2"))
        }
    }
    .onErrorReturn { t: Throwable ->
        Log.d("onComplete", t.localizedMessage)
        5
    }.subscribe(object : Observer<Int> {
        override fun onComplete() {
            Log.d("onComplete", "Completed")
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(i: Int) {
            Log.d("onNext", i.toString())
        }
        override fun onError(e: Throwable) {
            Log.d("onError", e.localizedMessage)
        }
    })


Output:

onComplete: Exception on 2
onNext: 5
onComplete: Completed

The onNext gets called and prints 5 after catching the exception and then completes the flow. We can see we have passed the value 5 in the onErrorReturn operator. If we don’t pass a value we will get a default value.

This way we can handle errors, particularly in RxJava.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads