neonDion
neonDion

Reputation: 2358

Retrofit2 + RxJava error handling

I am using RxJava and Retrofit2 (with OkHttp as the HTTP client) to do networking and am trying to understand how different errors are handled by Retrofit2 and how they look from the RxJava side. The following code illustrates an RxJava Subscriber callback for a network call (made with Retrofit).

        Subscription subscription = observable
            .subscribeOn(mScheduler)
            .observeOn(mAndroidScheduler)
            .subscribe(new Subscriber<User>() {
                @Override
                public void onCompleted() {
                    Timber.d("onCompleted called");
                    mRetainerView.clearUserObservable();
                    mActivityView.hideProgressBar();
                    mActivityView.enableUi();
                }

                @Override
                public void onError(Throwable e) {
                    Timber.d("onError called");
                    Timber.d(e.toString());
                    mRetainerView.clearUserObservable();
                    mActivityView.hideProgressBar();
                    mActivityView.enableUi();
                }

                @Override
                public void onNext(User user) {
                    Timber.d("onNext called");
                    mRetainerView.clearUserObservable();
                    mActivityView.hideProgressBar();
                    mActivityView.enableUi();
                    mActivityView.launchMainActivity();
                }
            });

My question is, in what cases will onError() be called and once it's been called, how can I interrogate the Throwable to determine the cause?

From the Retrofit source it looks like the only Throwables that are possible to see are IOException and HttpException. Can anyone verify that that is true?

Upvotes: 5

Views: 5889

Answers (3)

Enamul Haque
Enamul Haque

Reputation: 5063

In Kotlin I have used bellow like..

disposable.add(apiService.getLogin_service(parment1,parment1)
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribeWith(object : DisposableSingleObserver<Login_Reg_Data_Model>() {
            override fun onSuccess(model: Login_Reg_Data_Model) {
               //success
            }

            override fun onError(e: Throwable) {  


                if (e is HttpException) {
                    // We had non-200 http error
                    Log.e("time exceptionr******>",e.message)
                } else if (e is SocketTimeoutException) {
                    //time exception
                    Log.e("time exception******>",e.message)
                } else if (e is IOException) {
                    // A network error
                    Log.e("network error******>",e.message)
                } else {
                    //unknown error
                    Log.e("unknown error******>",e.message)
                }


            }

        })
    )

Upvotes: 1

Aitor Viana
Aitor Viana

Reputation: 933

Do not use onError for flow. That'd be as bad as try-catch for flow.

Error HTTP codes, are valid responses and you should not deal with them in onError. You can wrap the return type of your Retrofit services in Result, that gives you the means to get information about what happen with your call without throwing exceptions.

You can handle the state of your app using this pattern:

    service.getSomething()
        .map(r -> Model.success(r.response()))
        .onErrorReturn(Model::error)
        .observeOn(AndroidSchedulers.mainThread())
        .startWith(Resource.loading())
        .subscribe(r -> {
            myProgressBar.setVisible(r.isLoading());
            if (r.isSuccess()) {
                handleSuccess(); // e.g. 400 is also success but needs handling
            }
            if (r.isError()) {
                handleError();
            }
        }, OnErrorNotImplementedException::new);

See how I tried to handle all possible states within the stream and deliberately I throw OnErrorNotImplementedException for something I might've missed. This is very personal but I prefer to crash-fast-and-furious rather than being in an unknown state for a while that later will manifest in a crash harder to debug.

Upvotes: 1

Vesko
Vesko

Reputation: 3760

Here's the basics: onError() will be called if:

  • the observable you're subscribing to throws an exception (e.g. you get an IOException while trying to read a file)
  • an exception is raised in your onNext() method.

If there's an exception in your onComplete(), RxJava will propagate an rx.exceptions.OnCompletedFailedException and if there's an exception in onError() - you'll get rx.exceptions.OnErrorFailedException.

That said, you can just probe the Throwable you receive in your onError() method for exceptions that you're expecting. For example you know that if your API call results in client error (4xx), Retrofit will wrap it into HttpException. If there's a timeout with the request you'll get a SocketTimeoutException. Here's a rough example:

@Override
public void onError(Throwable e) {
    Timber.d("onError called");
    Timber.d(e.toString());
    handleError(e);
}

private handleError(Throwable throwable) {
    if (throwable instanceof HttpException) {
        HttpException httpException = (HttpException)throwable;
        int statusCode = httpException.code();
        // handle different HTTP error codes here (4xx)
    } else if (throwable instanceof SocketTimeoutException) {
        // handle timeout from Retrofit
    } else if (throwable instanceof IOException) {
       // file was not found, do something
    } else {
       // generic error handling
       mRetainerView.clearUserObservable();
       mActivityView.hideProgressBar();
       mActivityView.enableUi();
}

Upvotes: 10

Related Questions