Core Android

Handle all types of Retrofit2 errors properly with RxJava2

Google+ Pinterest LinkedIn Tumblr

During app development, We are always faced error and exception and crashes due to APIs failures. As a native mobile developer, it very important to ensure the app is never crash at the end user. You guys also aware Retrofit with RxJava is mostly used for calling external APIs In this tutorials, I’m going to write a solution for handle all type error while integrating external APIs in single place.

Basically, the mobile application is dependent on the API development process and doesn’t guarantee the APIs response will be always expected. If the problem is backend then they prepare a patch of containing bug fix and deployed. But in the mobile app development, it’s not happened. You depend on review times of Google Play/App Store if you need to quickly deploy a patch containing a bug fix. In this case, you also need your users to update the app after the approval to get it fixed.

The following API instability occur while integrating Rest APIs

  1. NullPointerException
  2. Manage HttpException
    • Forbidden Request
    • Internal Server Error
    • Unauthorized Exception
    • Check for “Bad Request” and throw IncorrectLoginPasswordException
    • ConnectionException – No Internet
    • Any other error is just NetworkException
  3. While APIs response format is not expected. eg, JsonSyntaxException BEGIN_OBJECT but was BEGIN_ARRAY, Expected BEGIN_OBJECT but was STRING

1. NullPointerException

NullPointerException is most common exception occurs when you performing operation of null object. Let’s suppose our app has a screen containing RecyclerView that showing feed data from a model class. The model class is simple POJO that containing the following attributes described by the API.

public class Row {

    @SerializedName("title")
    @Expose
    private String title;
    @SerializedName("description")
    @Expose
    private String description;
    @SerializedName("imageHref")
    @Expose
    private String imageHref;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getImageHref() {
        return imageHref;
    }

    public void setImageHref(String imageHref) {
        this.imageHref = imageHref;
    }
}

In the screen we have decided to show the title with uppercase letters. like below

TextView titleView = (TextView) findViewById(R.id.tvTitle);
titleView.setText(row.getTitle().toUpperCase());

Problem Statement

The app now is working fine as expected, you submitted at Google Play and user are happy to using it. Now let’s suppose APIs stop the sending title parameter. then what happen .? The app will crash when trying to show the title because we are applying toUpperCase () method on null object.

Proposed Solution

The proposed solution validates the model before it’s sent to the view, and somehow validation failed, show the error message to the user. But runtime just checking every property against null, not a good practice. It’s affected your app performance. then how to do .?

We need to find out the way so we tell compiler at runtime which properties we want to be verified. We can do using annotations.

Annotations, a form of metadata, provide data about a program that is not part of the program itself.

Create model validator script

Go to src and create a file with IsDefined name

package com.androidwave.utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created on : Jan 13, 2019
 * Author     : AndroidWave
 * Email    : [email protected]
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IsDefined {
}

In this class we are using two annotations

  • Target(ElementType.FIELD) – That means the IsDefined annotation should be available at runtime
  • Retention(RetentionPolicy.RUNTIME) – It means the IsDefined will annotate class attributes.

Create a POJO validator class

Let’s create a POJO validator class that will receive a model object, search each attribute that annotated by IsDefined and if it has null value will throw an exception.

package com.androidwave.utils;

import android.support.annotation.NonNull;

import java.lang.reflect.Field;

/**
 * Created on : Jan 13, 2019
 * Author     : AndroidWave
 * Email    : [email protected]
 */
public class PojoValidator {
    private Object model;

    public PojoValidator(@NonNull Object model)  {
        this.model = model;
    }

    public void validate() throws IllegalArgumentException {

        if (model == null) {
            throw new IllegalArgumentException("Model cannot be null");
        }

        final Field[] modelFields = model.getClass().getDeclaredFields();

        for (Field modelField : modelFields) {
            validateIsDefinedField(modelField);
        }
    }

    private void validateIsDefinedField(Field field) {

        if (field.isAnnotationPresent(IsDefined.class)) {

            Object attributeValue = null;
            field.setAccessible(true);

            try {
                attributeValue = field.get(model);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            if (attributeValue == null) {
                throw new IllegalArgumentException(field + " is required");
            }

        }
    }
}

2. Manage HttpException

 subscription.add(
                service.getFeedResponse()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnTerminate(() -> {
                            if (view != null) {
                                view.onFetchDataCompleted();
                            }
                        })
                        .subscribe(feedResponse -> {
                            if (view != null) {
                                /**
                                 * Update view here
                                 */
                                view.onFetchDataSuccess(feedResponse);
                            }
                        }, error -> {
                           handleApiError(error);
                        })
        );
Definition of handle API error
 public void handleApiError(Throwable error) {
        if (error instanceof HttpException) {
            switch (((HttpException) error).code()) {
                case HttpsURLConnection.HTTP_UNAUTHORIZED:
                    mView.onError("Unauthorised User ");
                    break;
                case HttpsURLConnection.HTTP_FORBIDDEN:
                    mView.onError("Forbidden");
                    break;
                case HttpsURLConnection.HTTP_INTERNAL_ERROR:
                    mView.onError("Internal Server Error");
                    break;
                case HttpsURLConnection.HTTP_BAD_REQUEST:
                    mView.onError("Bad Request");
                    break;
                case API_STATUS_CODE_LOCAL_ERROR:
                    mView.onError("No Internet Connection");
                    break;
                default:
                    mView.onError(error.getLocalizedMessage());

            }
        }
    }

3. The APIs response format is not expected

This problem mostly occur during development going on, So I have written a separate article with complete source code, Read here Retrofit Globally Error Handling 🙂

Author

Write A Comment