Retrofit обработка ошибок

After going through a number of solutions. Am posting it for more dynamic use. Hope this will help you guys.

My Error Response

{
"severity": 0,
"errorMessage": "Incorrect Credentials (Login ID or Passowrd)"
}

Below is as usual call method

private void makeLoginCall() {
    loginCall = RetrofitSingleton.getAPI().sendLogin(loginjsonObject);
    loginCall.enqueue(new Callback<Login>() {
        @Override
        public void onResponse(Call<Login> call, Response<Login> response) {
            if (response != null && response.code() == 200){
                //Success handling
            }
            else if (!response.isSuccessful()){
                mServerResponseCode = response.code();
                Util.Logd("In catch of login else " + response.message());
                /*
                * Below line send respnse to Util class which return a specific error string
                * this error string is then sent back to main activity(Class responsible for fundtionality)
                * */

                mServerMessage = Util.parseError(response) ;
                mLoginWebMutableData.postValue(null);
                loginCall = null;
            }
        }

        @Override
        public void onFailure(Call<Login> call, Throwable t) {
            Util.Logd("In catch of login " + t.getMessage());
            mLoginWebMutableData.postValue(null);
            mServerMessage = t.getMessage();
            loginCall = null;
        }
    });
}

Below Is util class to handle parsing

public static String parseError(Response<?> response){
    String errorMsg = null;
    try {
        JSONObject jObjError = new JSONObject(response.errorBody().string());
        errorMsg = jObjError.getString("errorMessage");
        Util.Logd(jObjError.getString("errorMessage"));
        return errorMsg ;
    } catch (Exception e) {
        Util.Logd(e.getMessage());
    }
    return errorMsg;
}

Below in viewModel observer

private void observeLogin() {
    loginViewModel.getmLoginVModelMutableData().observe(this, login -> {
        if (loginViewModel.getSerResponseCode() != null) {
            if (loginViewModel.getSerResponseCode().equals(Constants.OK)) {
                if (login != null) {
                    //Your logic here
                }
            }
           //getting parsed response message from client to handling class
            else {
                Util.stopProgress(this);
                Snackbar snackbar = Snackbar.make(view, loginViewModel.getmServerVModelMessage(), BaseTransientBottomBar.LENGTH_INDEFINITE).setAction(android.R.string.ok, v -> { });
                snackbar.show();
            }
        } else {
            Util.stopProgress(this);
            Snackbar snackbar = Snackbar.make(view, "Some Unknown Error occured", BaseTransientBottomBar.LENGTH_INDEFINITE).setAction(android.R.string.ok, v -> { });
            snackbar.show();
        }
    });
}

Two weeks ago, you’ve seen how to log requests and responses for debugging purposes. Requests might not finish successfully and you have to take care of failure situations. Most of the time, you need to manually apply the correct action like showing an error message as user feedback. If you get more than just the response status code, you can use the additional data to set the user in the right context and provide more information about the current error situation. That’s what this post is about: how to apply simple error handling using Retrofit 2.

Retrofit Series Overview

  • Retrofit
  • Requests
  • Responses
  • Converters
  • Error Handling
  • Logging
  • Calladapters
  • Pagination
  • File Upload & Download
  • Authentication
  • Caching
  • Testing & Mocking
  • Java Basics for Retrofit

>

Error Handling Preparations

Even though you want your app to always work like expected and there shouldn’t be any issues while executing requests. However, you’re not in control of when servers will fail or users will put wrong data which results in errors returned from the requested API. In those cases, you want to provide as much feedback to the user required to set him/her into the right context so that he/she understands what the issue is.

Before diving into the actual request which results in an error, we’re going to prepare classes to parse the response body which contains more information.

Error Object

At first, we create the error object representing the response you’re receiving from your requested API. Let’s assume your API sends a JSON error body like this:

{
    statusCode: 409,
    message: "Email address already registered"
}

If we would just show the user a generic error message like There went something wrong, he/she would immediately be upset about this stupid app which isn’t able to show what went wrong.

To avoid these bad user experiences, we’re mapping the response body to a Java object, represented by the following class.

public class APIError {

    private int statusCode;
    private String message;

    public APIError() {
    }

    public int status() {
        return statusCode;
    }

    public String message() {
        return message;
    }
}

We don’t actually need the status code inside the response body, it’s just for illustration purposes and this way you don’t need to extra fetch it from the response.

Simple Error Handler

We’ll make use of the following class only having one static method which returns an APIError object. The parseError method expects the response as parameter. Further, you need to make your Retrofit instance available to apply the appropriate response converter for the received JSON error response.

public class ErrorUtils {

    public static APIError parseError(Response<?> response) {
        Converter<ResponseBody, APIError> converter = 
                ServiceGenerator.retrofit()
                        .responseBodyConverter(APIError.class, new Annotation[0]);

        APIError error;

        try {
            error = converter.convert(response.errorBody());
        } catch (IOException e) {
            return new APIError();
        }

        return error;
    }
}

We’re exposing our Retrofit instance from ServiceGenerator via static method (if you’re not familiar with the ServiceGenerator, please read the introductory post of this series). Please bear with us that we’re using a kind of hacky style by exposing the Retrofit object via static method. The thing that is required to parse the JSON error is the response converter. And the response converter is available via our Retrofit object.

At first, we’re getting the error converter from the ServiceGenerator.retrofit() instance by additionally passing our APIError class as the parameter to the responseBodyConverter method. The responseConverter method will return the appropriate converter to parse the response body type. In our case, we’re expecting a JSON converter, because we’ve received JSON data.

Further, we call converter.convert to parse the received response body data into an APIError object. Afterwards, we’ll return the created object.

Error Handler in Action

Retrofit 2 has a different concept of handling «successful» requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as «successful». That means, for these requests the onResponse callback is fired and you need to manually check whether the request is actual successful (status 200-299) or erroneous (status 400-599).

If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.

Call<User> call = service.me();  
call.enqueue(new Callback<User>() {  
    @Override
    public void onResponse(Call<User> call, Response<User> response) {
        if (response.isSuccessful()) {
            // use response data and do some fancy stuff :)
        } else {
            // parse the response body …
            APIError error = ErrorUtils.parseError(response);
            // … and use it to show error information

            // … or just log the issue like we’re doing :)
            Log.d("error message", error.message());
        }
    }

    @Override
    public void onFailure(Call<User> call, Throwable t) {
        // there is more than just a failing request (like: no internet connection)
    }
});

As you can see, we use the ErrorUtils class to parse the error body and get an APIError object. Use this object and the contained information to show a meaningful message instead of a generic error message.

Outlook

This article shows you a simple way to manage errors and extract information from the response body. Most APIs will send you specific information on what went wrong and you should make use of it.

This is just the tip of the iceberg when it comes to error handling. Within Retrofit 1, you had the opportunity to add a custom error handler. This option was removed from Retrofit 2 and we think it’s good the way it is. We’ll tell you about more advanced techniques on error handling with Retrofit 2 within a future blog post.

If you run into any issue or have a question, please let us know in the comments below or tweet us @futurestud_io.


Still Have Questions? Get Our Retrofit Book!

Retrofit Book

All modern Android apps need to do network requests. Retrofit offers you an extremely convenient way of creating and managing network requests. From asynchronous execution on a background thread, to automatic conversion of server responses to Java objects, Retrofit does almost everything for you. Once you’ve a deep understanding of Retrofit, writing complex requests (e.g., OAuth authentication) will be done in a few minutes.

Invest time to fully understand Retrofit’s principles. It’ll pay off multiple times in the future! Our book offers you a fast and easy way to get a full overview over Retrofit. You’ll learn how to create effective REST clients on Android in every detail.

Boost your productivity and enjoy working with complex APIs.

Android - Retrofit 2 Custom Error Response Handling

Usually, when using Retrofit 2, we have two callback listeners: onResponse and onFailure If onResponse is called, it doesn’t always mean that we get the success condition. Usually a response is considered success if the status scode is 2xx and Retrofit has already provides isSuccessful() method. The problem is how to parse the response body which most likely has different format. In this tutorial, I’m going to show you how to parse custom JSON response body in Retrofit 2.

Preparation

First, we create RetrofitClientInstance class for creating new instance of Retrofit client and the MyService interface which defines the endpoint we’re going to call.

RetrofitClientInstance.java

  public class RetrofitClientInstance {
      private static final String BASE_URL = "http://159.89.185.115:3500";

      public static Retrofit getRetrofitInstance() {
            return new retrofit2.Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
      }
  }

MyService.java

  public interface MyServie {
      @GET("/api/items")
      Call<List> getItems();
  }

Let’s say we have an endpoint /api/items which in normal case, it returns the list of Item objects.

Item.java

  public class Item {
      private String id;
      private String name;

      public Item(String id, String name) {
          this.id = id;
          this.name = name;
      }

     /* Getter and Setter here */
  }

Below is the standard way to call the endpoint.

Example.java

  MyService myService = RetrofitClientInstance
          .getRetrofitInstance()
          .create(MyService.class);
  Call<List<Item>> call = retailService.getItems();

  call.enqueue(new Callback<List<Item>>() {
      @Override
      public void onResponse(final Call<List<Item>> call, final Response<List<Item>> response) {
          List<Item> items = response.body();
          Toast.makeText(getContext(), "Success").show();
      }

      @Override
      public void onFailure(final Call call, final Throwable t) {
          Toast.makeText(getContext(), "Failed").show();
      }
  });

Parsing Error Response

The problem is how to parse the response if it’s not success. The idea is very simple. We need to create custom util for parsing the error. For example, we know that the server will return a JSON object with the following structure when an error occurs.

  {
    "success": false,
    "errors": [
      "Error message 1",
      "Error message 2"
    ]
  }

We need to create a util for parsing the error. It returns an Object containing parsed error body. First, we define the class which represents the parsed error.

APIError.java

  public class APIError {
      private boolean success;
      private ArrayList messages;

      public static class Builder {
          public Builder() {}

          public Builder success(final boolean success) {
              this.success = success;
              return this;
          }

          public Builder messages(final ArrayList messages) {
              this.messages = messages;
              return this;
          }

          public Builder defaultError() {
              this.messages.add("Something error");
              return this;
          }

          public APIError build() { return new APIError(this); }
      }

      private APIError(final Builder builder) {
          success = builder.successs;
          messages = builder.messages;
      }
  }

And here’s the util for parsing the response body. If you have different response body format, you can adjust it to suit your case.

ErrorUtils.java

  public class ErrorUtils {
      public static APIError parseError(final Response<?> response) {
          JSONObject bodyObj = null;
          boolean success;
          ArrayList messages = new ArrayList<>();

          try {
              String errorBody = response.errorBody().string();

              if (errorBody != null) {
                  bodyObj = new JSONObject(errorBody);

                  success = bodyObj.getBoolean("success");
                  JSONArray errors = bodyObj.getJSONArray("errors");

                  for (int i = 0; i < errors.length(); i++) {
                      messages.add(errors.get(i));
                  }
              } else {
                  success = false;
                  messages.add("Unable to parse error");
              }
          } catch (Exception e) {
              e.printStackTrace();

              success = false;
              messages.add("Unable to parse error");
          }

          return new APIError.Builder()
                  .success(false)
                  .messages(messages)
                  .build();
      }
  }

Finally, change the onResponse and onFailure methods. If response.isSuccessful() is false, we use the error parser. In addition, if the code go through onFailure which most likely we’re even unable to get the error response body, just return the default error.

Example.java

  MyService myService = RetrofitClientInstance
          .getRetrofitInstance()
          .create(MyService.class);
  Call<List<Item>> call = retailService.getItems();

  call.enqueue(new Callback<List<Item>>() {
      @Override
      public void onResponse(final Call<List<Item>> call, final Response<List<Item>> response) {
          if (response.isSuccessful()) {
              List<Item> items = response.body();
              Toast.makeText(getContext(), "Success").show();
          } else {
              apiError = ErrorUtils.parseError(response);
              Toast.makeText(getContext(), R.string.cashier_create_failed,
                      Toast.LENGTH_LONG).show();
          }
      }

      @Override
      public void onFailure(final Call<Cashier> call, final Throwable t) {
          apiError = new APIError.Builder().defaultError().build();
          Toast.makeText(getContext(), "failed").show();
      }
  });

That’s how to parse error body in Retrofit 2. If you also need to define custom GSON converter factory, read this tutorial.

In the previous article, I have discussed the way to handling for NullPointerException, HttpException during REST API calls. In this post, I’m going to explain errors handling android in a single place.

Every developer wants to ensure our app will never crash, even end-user also want same. So we will catch all non-success responses from the server and handled the single place.

Retrofit is a very powerful library for networking in Android these days. While integrating REST APIs doesn’t guarantee the APIs response will be always expected. So how to manage these kinds of Exception?

Problem Use Case

Suppose we are integrating a REST API that gets the user profile from server and displaying our app and JSON like below.

User API response JSON

{
  "error": false,
  "message": "User Profile Details Found",
  "statusCode": 200,
  "data": {
    "userId": "dpPnxRI3n",
    "userName": "monika.sharma",
    "firstName": "Monika",
    "lastName": "Sharma",
    "bio": "Tech Lead/Architect",
    "mobileNumber": "91 9527169942",
    "location": [
      {
        "city": "Agra",
        "country": "India",
        "geoLocation": "",
        "state": "UP"
      }
    ],
    "profilePicUrl": "http://35.197.65.22/wp-content/uploads/2019/01/profile_pic.jpg",
    "designation": "Assistent Manager",
    "workAt": "Unify System Pvt. Ltd.",
    "about": " I enjoy working on projects for innovation, profitability and scalability",
    "followersCounter": 110,
    "followingCount": 110
  },
  "authToken": ""
}

Now you have create a POJO for parsing this JSON object.

package com.androidwave.errorhandling.network.pojo;

import com.google.gson.annotations.SerializedName;

import java.util.List;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class UserProfile {

    @SerializedName("about")
    private String mAbout;
    @SerializedName("bio")
    private String mBio;
    @SerializedName("channelCount")
    private Long mChannelCount;
    @SerializedName("designation")
    private String mDesignation;
    @SerializedName("firstName")
    private String mFirstName;
    @SerializedName("followersCounter")
    private Long mFollowersCounter;
    @SerializedName("followingCount")
    private Long mFollowingCount;
    @SerializedName("lastName")
    private String mLastName;
    @SerializedName("location")
    private List<Place> mLocation;
    @SerializedName("mobileNumber")
    private String mMobileNumber;
    @SerializedName("profilePicUrl")
    private String mProfilePicUrl;
    @SerializedName("studiedAt")
    private String mStudiedAt;
    @SerializedName("userId")
    private String mUserId;
    @SerializedName("userName")
    private String mUserName;
    @SerializedName("workAt")
    private String mWorkAt;

    public String getAbout() {
        return mAbout;
    }

    public void setAbout(String about) {
        mAbout = about;
    }

    public String getBio() {
        return mBio;
    }

    public void setBio(String bio) {
        mBio = bio;
    }

    public Long getChannelCount() {
        return mChannelCount;
    }

    public void setChannelCount(Long channelCount) {
        mChannelCount = channelCount;
    }

    public String getDesignation() {
        return mDesignation;
    }

    public void setDesignation(String designation) {
        mDesignation = designation;
    }

    public String getFirstName() {
        return mFirstName;
    }

    public void setFirstName(String firstName) {
        mFirstName = firstName;
    }

    public Long getFollowersCounter() {
        return mFollowersCounter;
    }

    public void setFollowersCounter(Long followersCounter) {
        mFollowersCounter = followersCounter;
    }

    public Long getFollowingCount() {
        return mFollowingCount;
    }

    public void setFollowingCount(Long followingCount) {
        mFollowingCount = followingCount;
    }


    public String getLastName() {
        return mLastName;
    }

    public void setLastName(String lastName) {
        mLastName = lastName;
    }

    public List<Place> getLocation() {
        return mLocation;
    }

    public void setLocation(List<Place> location) {
        mLocation = location;
    }

    public String getMobileNumber() {
        return mMobileNumber;
    }

    public void setMobileNumber(String mobileNumber) {
        mMobileNumber = mobileNumber;
    }

    public String getProfilePicUrl() {
        return mProfilePicUrl;
    }

    public void setProfilePicUrl(String profilePicUrl) {
        mProfilePicUrl = profilePicUrl;
    }

    public String getStudiedAt() {
        return mStudiedAt;
    }

    public void setStudiedAt(String studiedAt) {
        mStudiedAt = studiedAt;
    }

    public String getUserId() {
        return mUserId;
    }

    public void setUserId(String userId) {
        mUserId = userId;
    }

    public String getUserName() {
        return mUserName;
    }

    public void setUserName(String userName) {
        mUserName = userName;
    }

    public String getWorkAt() {
        return mWorkAt;
    }

    public void setWorkAt(String workAt) {
        mWorkAt = workAt;
    }
}

We are using GSON parsing with RxJava2CallAdapterFactory converter for the POJO, for now, API starts sending location Object as a string array in an instance of Place Object ( see below JSON ) resulted from app again start crashing because the converter is throwing JsonSyntaxException. how to resolve this problem so our app will never crash.

API response in case of location is string array

{
  "error": false,
  "message": "User Profile Details Found",
  "statusCode": 200,
  "data": {
    "userId": "dpPnxRI3n",
    "userName": "monika.sharma",
    "firstName": "Monika",
    "lastName": "Sharma",
    "bio": "Tech Lead/Architect",
    "mobileNumber": "91 9527169942",
    "location": [
      "Agra UP India "
    ],
    "profilePicUrl": "http://35.197.65.22/wp-content/uploads/2019/01/profile_pic.jpg",
    "designation": "Assistent Manager",
    "workAt": "Unify System Pvt. Ltd.",
    "about": " I enjoy working on projects for innovation, profitability and scalability",
    "followersCounter": 110,
    "followingCount": 110
  },
  "authToken": ""
}

Solution

When you integrate API using Retrofit you get to check error three times isSuccessful, IOException, and the response code. It increases the line of code unusually. Manage all this create a base wrapper class. I will tell you error handling android in a single place by using below wrapper class.

Create a response wrapper class

For parsing APIs response create a Wrapper class in src folder names WrapperResponse

package com.androidwave.errorhandling.network.pojo;

import com.google.gson.annotations.SerializedName;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class WrapperResponse<T> {
    @SerializedName("data")
    private T mData;
    @SerializedName("error")
    private Boolean mError;
    @SerializedName("message")
    private String mMessage;
    @SerializedName("status")
    private String mStatus;
    @SerializedName("authToken")
    private String mAuthToken;

    public String getAuthToken() {
        return mAuthToken;
    }

    public void setAuthToken(String mAuthToken) {
        this.mAuthToken = mAuthToken;
    }

    public T getData() {
        return mData;
    }

    public void setData(T data) {
        mData = data;
    }

    public Boolean getError() {
        return mError;
    }

    public void setError(Boolean error) {
        mError = error;
    }

    public String getMessage() {
        return mMessage;
    }

    public void setMessage(String message) {
        mMessage = message;
    }

    public String getStatus() {
        return mStatus;
    }

    public void setStatus(String status) {
        mStatus = status;
    }
}
Here is T is TYPE of class that you want to parse in our case is UserProfile

Create a error wrapper class

In src folder a create a wrapper class with named

package com.androidwave.errorhandling.network;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class WrapperError extends RuntimeException {


    @Expose
    @SerializedName("status_code")
    private Long statusCode;

    @Expose
    @SerializedName("message")
    private String message;

    public WrapperError(Long statusCode, String message) {
        this.statusCode = statusCode;
        this.message = message;
    }


    public WrapperError(Long statusCode) {
        this.statusCode = statusCode;
    }


    public Long getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(Long statusCode) {
        this.statusCode = statusCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

Create a JSON Converter Factory

Gson provides powerful parsing of JSON object so I will be creating a dynamic type. Suppose you want to parse UserProfile I jut have to tell Gson to parse response as a WrapperResponse<UserProfile> and go from there. If you think Why would you create a custom JSON converter factory. I will clear your all doubt, just for Abstraction, I’m packing all of this API parsing and wrapping code into a single package and hide it from the rest of the application.

package com.androidwave.errorhandling.network;

import com.androidwave.errorhandling.network.pojo.WrapperResponse;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class WrapperConverterFactory extends Converter.Factory {

    private GsonConverterFactory factory;

    public WrapperConverterFactory(GsonConverterFactory factory) {
        this.factory = factory;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(final Type type,
                                                            Annotation[] annotations, Retrofit retrofit) {
        // e.g. WrapperResponse<UserProfile>
        Type wrappedType = new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[]{type};
            }

            @Override
            public Type getOwnerType() {
                return null;
            }

            @Override
            public Type getRawType() {
                return WrapperResponse.class;
            }
        };
        Converter<ResponseBody, ?> gsonConverter = factory
                .responseBodyConverter(wrappedType, annotations, retrofit);
        return new WrapperResponseBodyConverter(gsonConverter);
    }
}

Create a response body converter

package com.androidwave.errorhandling.network;

import com.androidwave.errorhandling.network.pojo.WrapperResponse;

import java.io.IOException;

import okhttp3.ResponseBody;
import retrofit2.Converter;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class WrapperResponseBodyConverter<T>
        implements Converter<ResponseBody, T> {
    private Converter<ResponseBody, WrapperResponse<T>> converter;

    public WrapperResponseBodyConverter(Converter<ResponseBody,
            WrapperResponse<T>> converter) {
        this.converter = converter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        WrapperResponse<T> response = converter.convert(value);
        if (!response.getError()) {
            return response.getData();
        }
        // RxJava will call onError with this exception
        throw new WrapperError(response.getStatus(), response.getMessage());
    }
}

Set the WrapperConverterFactory in Retrofit client

Now we will user WrapperConverterFactory instance of GsonConverterFactory. As I explain In this demo we are using Dagger2 + RxJava Retrofit with MVP design pattern. So Just open application module and change below in Retrofit Client

 /**
     * 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(new WrapperConverterFactory(GsonConverterFactory.create()))
                .build();
    }
For better understanding I’m show full code of retrofit client
package com.androidwave.errorhandling.di.module;

import android.app.Application;
import android.content.Context;

import com.androidwave.errorhandling.BuildConfig;
import com.androidwave.errorhandling.di.ApplicationContext;
import com.androidwave.errorhandling.network.NetworkService;
import com.androidwave.errorhandling.network.WrapperConverterFactory;
import com.androidwave.errorhandling.ui.MainMvp;
import com.androidwave.errorhandling.ui.MainPresenter;

import dagger.Module;
import dagger.Provides;
import io.reactivex.disposables.CompositeDisposable;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
@Module
public class ApplicationModule {

    private final Application mApplication;

    public ApplicationModule(Application application) {
        mApplication = application;
    }

    @Provides
    @ApplicationContext
    Context provideContext() {
        return mApplication;
    }

    @Provides
    Application provideApplication() {
        return mApplication;
    }

    /**
     * @return HTTTP Client
     */
    @Provides
    public OkHttpClient provideClient() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        return new OkHttpClient.Builder().addInterceptor(interceptor).addInterceptor(chain -> {
            Request request = chain.request();
            return chain.proceed(request);
        }).build();
    }

    /**
     * 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(new WrapperConverterFactory(GsonConverterFactory.create()))
                .build();
    }

    /**
     * Provide Api service
     *
     * @return ApiService instances
     */

    @Provides
    public NetworkService provideNetworkService() {
        return provideRetrofit(BuildConfig.BASE_URL, provideClient()).create(NetworkService.class);
    }

    @Provides
    CompositeDisposable provideCompositeDisposable() {
        return new CompositeDisposable();
    }

    @Provides
    public MainMvp.Presenter provideMainPresenter(NetworkService mService, CompositeDisposable disposable) {
        return new MainPresenter(mService, disposable);
    }
}
Create a interface for get user details like below
package com.androidwave.errorhandling.network;

import com.androidwave.errorhandling.network.pojo.UserProfile;
import com.androidwave.errorhandling.network.pojo.WrapperResponse;

import io.reactivex.Observable;
import retrofit2.http.GET;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public interface NetworkService {
    /**
     * @return Observable feed response
     */
    @GET("user.php")
    Observable<UserProfile> getUserProfile();
}

Let’s create MVP Contract for main activity, Normally create in MVP pattern

package com.androidwave.errorhandling.ui;

import com.androidwave.errorhandling.network.pojo.UserProfile;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 * Email    : info@androidwave.com
 */
public class MainMvp {
    interface View {

        void showLoading(boolean isLoading);

        void onSuccess(UserProfile mProfile);

        void onError(String message);
    }

    public interface Presenter {

        void getUserProfile();

        void detachView();

        void attachView(View view);

        void handleApiError(Throwable error);

    }
}

In UI package create Presenter which implements MainMvp.Presenter

You already aware of the uses of Presenter the more important part void handleApiError(Throwable error); For best practice, you should create BasePresenter for implementing handleApiError() method and all child class will extend BasePresenter.

package com.androidwave.errorhandling.ui;


import com.androidwave.errorhandling.network.NetworkService;
import com.androidwave.errorhandling.network.WrapperError;
import com.google.gson.JsonSyntaxException;

import javax.inject.Inject;
import javax.net.ssl.HttpsURLConnection;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import retrofit2.HttpException;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 */
public class MainPresenter implements MainMvp.Presenter {
    public static final int API_STATUS_CODE_LOCAL_ERROR = 0;
    private CompositeDisposable mDisposable;
    private NetworkService mService;
    private MainMvp.View mView;
    private static final String TAG = "MainPresenter";

    @Inject
    public MainPresenter(NetworkService service, CompositeDisposable disposable) {
        this.mService = service;
        this.mDisposable = disposable;
    }


    @Override
    public void getUserProfile() {
        if (mView != null) {
            mView.showLoading(true);
        }
        mDisposable.add(
                mService.getUserProfile()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnTerminate(() -> {
                            if (mView != null) {
                                mView.showLoading(false);
                            }
                        })
                        .subscribe(response -> {
                            if (mView != null) {
                                mView.showLoading(false);
                                /**
                                 * Update view here
                                 */
                                mView.onSuccess(response);
                            }
                        }, error -> {
                            if (mView != null) {
                                mView.showLoading(false);
                                /**
                                 * manage all kind of error in single place
                                 */
                                handleApiError(error);
                            }
                        })
        );
    }

    @Override
    public void detachView() {
        mDisposable.clear();
    }

    @Override
    public void attachView(MainMvp.View view) {
        this.mView = view;
    }

    @Override
    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());

            }
        } else if (error instanceof WrapperError) {
            mView.onError(error.getMessage());
        } else if (error instanceof JsonSyntaxException) {
            mView.onError("Something Went Wrong API is not responding properly!");
        } else {
            mView.onError(error.getMessage());
        }

    }
}

Implement view in activity like below

package com.androidwave.errorhandling.ui;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.androidwave.errorhandling.R;
import com.androidwave.errorhandling.WaveApp;
import com.androidwave.errorhandling.network.pojo.Place;
import com.androidwave.errorhandling.network.pojo.UserProfile;
import com.androidwave.errorhandling.utils.CommonUtils;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity implements MainMvp.View {

    //  ActivityComponent mActivityComponent;
    private static final String TAG = "MainActivity";
    @Inject
    MainMvp.Presenter mPresenter;
    @BindView(R.id.txtTitle)
    TextView txtTitle;
    @BindView(R.id.txtDesignation)
    TextView txtDesignation;
    @BindView(R.id.txtFollowers)
    TextView txtFollowers;
    @BindView(R.id.txtFollowing)
    TextView txtFollowing;
    @BindView(R.id.txtUsername)
    TextView txtUsername;
    @BindView(R.id.txtBio)
    TextView txtBio;
    @BindView(R.id.txtPhone)
    TextView txtPhone;
    @BindView(R.id.txtAddress)
    TextView txtAddress;
    @BindView(R.id.txtWorkAt)
    TextView txtWorkAt;
    @BindView(R.id.imageViewProfilePic)
    ImageView imageViewProfilePic;
    private ProgressDialog mDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ((WaveApp) getApplication()).getComponent().inject(this);
        mPresenter.attachView(this);
        mPresenter.getUserProfile();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void showLoading(boolean isLoading) {
        if (isLoading) {
            mDialog = CommonUtils.showLoadingDialog(this);
        } else {
            if (mDialog != null)
                mDialog.dismiss();
        }
    }

    @Override
    public void onSuccess(UserProfile mProfile) {
        txtTitle.setText(String.format("%s %s", mProfile.getFirstName(), mProfile.getLastName()));
        txtDesignation.setText(mProfile.getDesignation());
        txtFollowers.setText(String.valueOf(mProfile.getFollowersCounter()));
        txtFollowing.setText(String.valueOf(mProfile.getFollowingCount()));
        txtUsername.setText(mProfile.getUserName());
        txtPhone.setText(mProfile.getMobileNumber());
        txtWorkAt.setText(mProfile.getWorkAt());
        txtBio.setText(mProfile.getBio());
        Place mPlace = mProfile.getLocation().get(0);
        txtAddress.setText(String.format("%s %s %s", mPlace.getCity(), mPlace.getState(), mPlace.getCountry()));

        Glide.with(MainActivity.this).load(mProfile.getProfilePicUrl()).apply(new RequestOptions().centerCrop().circleCrop().placeholder(R.drawable.profile_pic)).into(imageViewProfilePic);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    @Override
    public void onError(String message) {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),
                message, Snackbar.LENGTH_SHORT);
        View sbView = snackbar.getView();
        TextView textView = sbView
                .findViewById(android.support.design.R.id.snackbar_text);
        textView.setTextColor(ContextCompat.getColor(this, R.color.white));
        snackbar.show();
    }
}

After following all above just RUN the project and use app, If you have any queries, feel free to ask them in the comment section below.

For the best android app architect read our article MVP Architect Android apps with Dagger 2, Retrofit & RxJava 2.

If you are making synchronous request, you define your request method in the interface as Call<List<Car>>.

Once you execute the request you receive response and deserialized data wrapped in Response<T> as Response<List<Car>>. This wrapped gives you access to headers, http codes and raw response body.

You can access error body as:

 Call<List<Car>> carsCall = carInterface.loadCars();

   try {
      Response<List<Car>> carsResponse = carsCall.execute();
        } catch (IOException e) {
           e.printStackTrace();
           //network Exception is throw here
        }
     if(carsResponse != null && !carsResponse.isSuccess() && carsReponse.errorBody() != null){
          // handle carsResponse.errorBody()
     } 

For async calls, you receive Throwable, if I/O exception is thrown during the network call:

Call<List<Car>> call = service.loadCars();
call.enqueue(new Callback<List<Car>>() {
    @Override
    public void onResponse(Response<List<Car>> response) {
        // Get result from response.body(), headers, status codes, etc
    }

    @Override
    public void onFailure(Throwable t) {
      //handle error 
    }
});

Понравилась статья? Поделить с друзьями:
  • Retro color rc 20 vst ошибка
  • Reshade ошибка unable to download archive
  • Resetting smb shares ошибка
  • Researchdownload ошибка repartition failed operation failed
  • Researchdownload ошибка failed wait input time out