This article introduces several ways RxJava can handle business exceptions and share them with you. The details are as follows:
About exceptions
Java exceptions can be divided into two types: runtime exceptions and checking exceptions.
Runtime exception:
The RuntimeException class and its subclasses are called runtime exceptions. The characteristic of this exception is that the Java compiler does not check it. That is to say, when such an exception may occur in the program, even if it is not captured with the try...catch statement, it is not declared with the throws statement, and it will still be compiled and passed.
Checking abnormalities:
Except for RuntimeException and its subclasses, other Exception classes and their subclasses are all checking exceptions. Inspective exceptions must be explicitly caught or passed. When a checking exception may occur in the program, either use the try-catch statement to capture it or throw it with the throws clause, otherwise the compilation will not be passed.
Handle business exceptions
Business exception:
It refers to the exception thrown by the process cannot continue due to special requirements of certain businesses during normal business processing. The exception is thrown in the service layer or business processing method, intercept the exception in the presentation layer, and feedback it to the user in a friendly manner so that it can correctly complete the processing of the task function based on the prompt information.
1. Try again
Not all errors need to be immediately fed back to the user. For example, if an interface is called in a weak network environment, it may take a timeout. The data may be obtained by requesting the interface again. Then trying again is equivalent to giving the other party one more chance.
Here we use the retryWhen operator, which passes the error to another observer to decide whether to resubscribe to this observer.
It sounds a bit difficult to talk about, just put the code.
/** * Get content * @param fragment * @param param * @param cacheKey * @return */ public Maybe<ContentModel> getContent(Fragment fragment, ContentParam param, String cacheKey) { if (apiService == null) { apiService = ().apiService(); } return (param) .retryWhen(new RetryWithDelay(3,1000)) .compose((fragment).<ContentModel>toLifecycleTransformer()) .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey)); }
This example is a network request, and the content of the compose can be ignored. If the network request fails, the retryWhen operator will be called. RetryWithDelay implements the Function interface. RetryWithDelay is a retry mechanism that includes the number of retry times and the time interval of retry.
import ; import ; import ; import ; import ; import ; /** * Retry mechanism * Created by tony on 2017/11/6. */ public class RetryWithDelay implements Function<Flowable<? extends Throwable>, Publisher<?>> { private final int maxRetries; private final int retryDelayMillis; private int retryCount; public RetryWithDelay(final int maxRetries, final int retryDelayMillis) { = maxRetries; = retryDelayMillis; = 0; } @Override public Publisher<?> apply(@NonNull Flowable<? extends Throwable> attempts) throws Exception { return (new Function<Throwable, Publisher<?>>() { @Override public Publisher<?> apply(Throwable throwable) throws Exception { if (++retryCount <= maxRetries) { ("RetryWithDelay", "get error, it will try after " + retryDelayMillis + " millisecond, retry count " + retryCount); // When this Observable calls onNext, the original // Observable will be retried (. re-subscribed). return (retryDelayMillis, ); } else { // Max retries hit. Just pass the error along. return (throwable); } } }); } }
If you are lucky and try again successfully, the user can continue to use the product without perception. If multiple retry fails, then some exception processing must be done when onError, prompting the user that it may be the reason for the network.
2. Return a default value
Sometimes an error only needs to return a default value, which is a bit similar to orElse() of Java 8 Optional
() .adService() .vmw(param) .compose((fragment).<VMWModel>toLifecycleTransformer()) .subscribeOn(()) .onErrorReturn(new Function<Throwable, VMWModel>() { @Override public VMWModel apply(Throwable throwable) throws Exception { return new VMWModel(); } });
The above example uses the onErrorReturn operator, indicating that when an error occurs, a default value is emitted and the data flow is ended. Therefore, Subscriber cannot see the exception information, and what he sees is the normal ending state of the data flow.
Similar to it, there is the onErrorResumeNext operator, which means that when an error occurs, another data stream will be used to continue transmitting data. The error message is not visible in the returned observer.
After using onErrorReturn, will the onError be processed? onErrorReturn does return a default value. If there is an operation similar to doOnNext after onErrorReturn and an error occurs in doOnNext, onError will still work.
I once encountered a complex business scenario that requires multiple network requests to merge the results. At this time, I use the zip operator to let the requests be processed in parallel, and then merge them after all the requests are finished. If some requests fail, I use the retry mechanism, and if some requests fail, I give the default value.
3. Use onError to handle exceptions
In today's Android development, network frameworks are the world of Retrofit. In the return type defined by the interface, I generally like to use Maybe and Complete instead of Observable.
We know that when RxJava is used, observers will call onNext, onError, and onComplete methods, where the onError method is called after an error occurs during the process of passing or processing.
The following code encapsulates the Observers of two base classes, and both rewrites the onError method to handle various network exceptions. Observers for these two base classes are used when using Retrofit.
Encapsulate a BaseMaybeObserver
import import import import import import import /** * Created by Tony Shen on 2017/8/8. */ abstract class BaseMaybeObserver<T> : DisposableMaybeObserver<T>() { internal var mAppContext: Context init { mAppContext = () } override fun onSuccess(data: T) { onMaybeSuccess(data) } abstract fun onMaybeSuccess(data: T) override fun onError(e: Throwable) { var message = (message) when(e) { is ConnectException -> message = (.connect_exception_error) is SocketTimeoutException -> message = (.timeout_error) is UnknownHostException -> message = (.network_error) is NetworkErrorException -> message = (.network_error) else -> message = (.something_went_wrong) } ().post(FailedEvent(message)) } override fun onComplete() {} }
Encapsulate a BaseCompletableObserver
import import import import import import import /** * Created by Tony Shen on 2017/8/8. */ abstract class BaseCompletableObserver : ResourceCompletableObserver() { internal var mAppContext: Context init { mAppContext = () } override fun onComplete() { onSuccess() } abstract fun onSuccess() override fun onError(e: Throwable) { var message = (message) when(e) { is ConnectException -> message = (.connect_exception_error) is SocketTimeoutException -> message = (.timeout_error) is UnknownHostException -> message = (.network_error) is NetworkErrorException -> message = (.network_error) else -> message = (.something_went_wrong) } ().post(FailedEvent(message)) } }
Kotlin is used here to write these two base classes. The purpose of using Kotlin is to make the code more concise, avoid using switch or various if (XX instancof xxException) to determine exception types, which can be seamlessly combined with Java code.
The following code shows how to use BaseMaybeObserver, and even if an exception is encountered, the onError of BaseMaybeObserver will be handled accordingly. If you have special needs, you can also rewrite the onError method.
(,param, cacheKey) .compose(RxJavaUtils.<ContentModel>maybeToMain()) .doFinally(new Action() { @Override public void run() throws Exception { (); } }) .subscribe(new BaseMaybeObserver<ContentModel>(){ @Override public void onMaybeSuccess(ContentModel data) { (data); } });
4. Internal exceptions use the chain of responsibility mode to distribute
This is a method provided by a netizen in WeChat. He made a very interesting library for exception distribution. The github address is:/vihuela/Retrofitplus
Internal exceptions are distributed using the responsibility chain, and the distribution logic is:
- Custom exception -> Network exception -> Server exception -> Internal program exception -> Unknown exception
- In addition to the above custom exceptions, this library contains other exception distributions. The default adaptation scenario is: Rx+Json
- Please call custom exceptions, addCustomerParser method of ExceptionParseMgr class to add business exceptions
This library is not invasive to the original code. In addition, he also provides another idea, combining compose to handle some specific business exceptions.
Summarize
This article only summarizes the situation where personal business exceptions encountered in RxJava, and has dealt with it accordingly. It is definitely not possible to cover all aspects of development. It is just used as a guide. If there is a better and more elegant way of handling it, please let me know.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.