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.
- onExceptionResumeNext()
- onErrorResumeNext()
- doOnError()
- onErrorReturnItem()
- 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.
Similar Reads
Handling Errors in Spring WebFlux The Spring Boot Community Developed the Spring Reactive Web Framework. The SpringReactive allows developers to build asynchronous, non-blocking, and event-driven web applications. When compared with the Spring MVC framework the Spring Reactive Web framework provides more functionality. The Spring We
6 min read
Error Handling in Observables - Angular In Angular applications, we often work with asynchronous data streams, which are represented by Observables. While Observables provide a powerful way to handle asynchronous operations, it's important to handle errors that may occur during the subscription to prevent your application from crashing. E
4 min read
How to Handle rep.int Error in R To repeat items in a vector in R, one often uses the rep. int function. However, some factors might lead to problems while utilizing this function. Users can use rep. int to replicate items in vectors and debug problems including missing arguments, improper argument types, and mismatched vector leng
3 min read
How to Handle length Error in R Length errors in R typically occur when attempting operations on objects of unequal lengths. For example, adding two vectors of different lengths will result in an error. These errors can also be seen when operations are performed on arrays or other data structure. Hence, it is crucial to understand
4 min read
RxJava For Android RxJava is a JVM library that uses observable sequences to perform asynchronous and event-based programming. Its primary building blocks are triple O's, which stand for Operator, Observer, and Observables. And we use them to complete asynchronous tasks in our project. It greatly simplifies multithrea
4 min read