Error Handling Scheme for RxJava2

Recently, retrofit2 + rxKotlin2 was used to write interface access, trying to smoothen the code as much as possible, so when the status code returned by excuse is "unsuccessful" (e.g. code!= 200), it is unified in the onError method with network errors. The idea is always good, but in practice, onError failed to catch exceptions, resulting in application crash. Finally, this weekend, I sorted out the error handling mechanism of RxJava.

Every back-end interface will basically have a custom return code. We often judge whether the interface is successful in accessing and returning the data we want based on the return code. As a novice, I wrote this way:

	fun login(account: String, pwd: String) {
        showProgress("Landing")//Display chrysanthemums
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    if (it.code == 20000 && it.data != null) {
                        //Landing success!
						//Jump to Home Page
                    } else {
						//The landing failed!
						//Pop-up prompt
						//dismissProgress() Close Chrysanthemum
                    }
                }, {
                    dismissProgress()
                    //The landing failed!
					//Pop-up prompt
						//dismissProgress() Close Chrysanthemum
                })
    }

It's no problem to write this way, but it's sure to make the big guys laugh when they see it, because in onSuccess or onNext methods, there are both successful logic and failed logic, there are coupling, and the logic dealing with interface access failure is divided into two parts, that is, part of the failed logic is mixed into successful logic, then there is no way to unify it. What about the logic of access failure?

Using map Operator Skillfully to Decouple

	fun login(account: String, pwd: String) {
        showProgress("Landing")//Display chrysanthemums
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.map{ //Notice the map here.
					if (it.code != 20000 || it.data == null) {
						throw Exception("The landing failed!")
					}
					it
				}
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                        //Landing success!
						//Jump to Home Page
                }, {
                    dismissProgress()
                    //The landing failed!
					//Pop-up prompt
						//dismissProgress() Close Chrysanthemum
                })
    }

We see that before the thread switch, we can judge the return code in the map operator logic. If the return code indicates success, we return the original data directly and pass it to the onSuccess/onNext method. If the return code fails, we manually throw an exception, and then in the subscription, the onError method can catch the exception, so we throw it manually. Exceptions can be handled with requests timeout, 404 network and errors, while onSuccess or onNext methods only need to deal with the logic of successful access and successfully complete the decoupling. Does it seem clear?

One thing to note here is that the map logic must throw exception instead of returning exception directly. Otherwise rxjava will think that you want to convert the original data into Exception-type data and pass it to onSuccess/onNext method.

Direct tuning onError

Sometimes the business logic may be very simple, and the interface only contains the return code or other information representing the result of the request, but does not give specific actual data. At this time, wise people want to be lazy, so we can write as follows:

	fun login(account: String, pwd: String) {
        showProgress("Landing")//Display chrysanthemums
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
					if (it.code != 20000 || it.data == null) {
						onError(Exception("The landing failed!"))//Pay attention here.
					}
                    //Landing success!
					//Jump to Home Page
                }, {
                    dismissProgress()
                    //The landing failed!
					//Pop-up prompt
						//dismissProgress() Close Chrysanthemum
                })
    }

It is also possible to call onError directly and pass an exception when the return code fails. But if you throw directly like in the map operator, you can't. Because onError and onSuccess/onNoext are level, there is no other way to catch exceptions thrown directly in onSuccess/onNext, which can easily lead to application crashes.

map with onException Resume Next

	fun login(account: String, pwd: String) {
        showProgress("Landing")//Display chrysanthemums
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.map{ //Notice the map here.
					if (it.code != 20000 || it.data == null) {
						throw Exception("The landing failed!")
					}
					it
				}
				.onExceptionResumeNext {
                    service.register(account,pwd,it)//Converting login operations to registration operations
                }
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
					if (it.code != 20000 || it.data == null) {
						onError(Exception("Failure!"))//Pay attention here.
					}
                    //Success!
					//Jump to Home Page
                }, {
                    dismissProgress()
                    //Failure!
					//Pop-up prompt
						//dismissProgress() Close Chrysanthemum
                })
    }

The on Exception Resume Next operator is similar to the flatmap flatmap operation in that it is used to transform the target. The difference is that onError Resume Next has the ability to catch exceptions and is called only after exceptions are caught. In the previous example, we used onError Resume Next to achieve the "automatic registration after login failure" effect.

onErrorResumeNext

The onError Resume Next operator is basically the same as onException Resume Next, but the difference is that the latter only captures Exception, while the former captures only Throwable. So onException Resume Next is preferred.

onErrorReturn

private fun test5() {
        Single.just(0)
                .map {
                    if (it == 0) {
                        throw Exception("Fuck")
                    }
                    1
                }
                .onErrorReturn {
                    2
                }
                .subscribe({
                    Log.d("test", it.toString())
                }, {
                    Log.e("test", it.message)
                })
    }

OnErrorReturn itself is similar to map in that it is used to transform the original data. The difference is that onErrorReturn also has the ability to catch exceptions and is called only after the exceptions are caught. OnErrorReturn can be understood as: after encountering an exception, return a default value to pass to the onNext method, and then call the onComplete method to terminate the launch. (This operator is so funny)

retry

private fun test12() {
        Observable.range(0, 100)
                .map {
                    if (it == 0) {
                        throw Exception("Fuck")
                    }
                    Log.d("test","Still being launched ${it}")
                    it
                }
                .retry { t1, t2 ->
                    t1 != 50
                }
                .subscribe({
                    Log.d("test", it.toString())
                }, {
                    Log.e("test", it.message)
                })
    }

The retry operator, as its name implies, is used to retry, but the conditions for retry are determined by us. So if you think about it, you will know that polling with this operator is very convenient. In addition to retry, rxjava also provides retryWhen, retryUntil and other similar operators, similar to retry, without explanation, interested students can see for themselves.

Keywords: network

Added by hush on Fri, 17 May 2019 11:39:34 +0300