Category

Android

Category

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. The APIs response format is not expected JsonSyntaxException eg. BEGIN_OBJECT but was BEGIN_ARRAY, Expected BEGIN_OBJECT but was STRING

1. NullPointerException

NullPointerException is most common exception is occurred when you performing operation of null object. Let’s suppose our app have a screen that 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    : info@androidwave.com
 */
@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    : info@androidwave.com
 */
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:
                    view.showError(error.getLocalizedMessage());
                    break;
                case HttpsURLConnection.HTTP_FORBIDDEN:
                   view.showError(error.getLocalizedMessage());
                    break;
                case HttpsURLConnection.HTTP_INTERNAL_ERROR:
                     view.showError(error.getLocalizedMessage());
                    break;
                case HttpsURLConnection.HTTP_BAD_REQUEST:
                     view.showError(error.getLocalizedMessage());
                    break;
                case API_STATUS_CODE_LOCAL_ERROR:
                    view.showError(error.getLocalizedMessage());
                    break;
                default:
                    view.onFetchDataError(error.getLocalizedMessage());

            }
        }
    }

3. The APIs response format is not expected

We are taking the same POJO for this problem. Suppose you are using GSON parsing with RxJava2CallAdapterFactory converter for the same POJO, for now, API starts sending GsonObject in an instance of string resulted from app again start crashing because the converter is throwing JsonSyntaxException. how to resolve this problem so our app will never crash

As our title suggesting we are using Retrofit + RxJava for integrating external APIs. eg.

 /**
     * provide Retrofit instances
     *
     * @param baseURL base url for api calling
     * @param client  OkHttp client
     * @return Retrofit instances
     */

    @Provides
    public Retrofit provideRetrofit(String baseURL, OkHttpClient client) {
        return new Retrofit.Builder()
                .baseUrl(baseURL)
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

It’s your homework 🙂