SoFunction
Updated on 2025-04-04

A brief discussion on several ways to deal with business exceptions by RxJava

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:

  1. Custom exception -> Network exception -> Server exception -> Internal program exception -> Unknown exception
  2. In addition to the above custom exceptions, this library contains other exception distributions. The default adaptation scenario is: Rx+Json
  3. 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.