preface

The MVP framework has been around for quite a while, and it’s always been a bit of a struggle to figure out how to handle the data in the background. Most of the companies because of the code is not standard, too many people and so on, the background code is quite chaotic, the data format returned by the interface is also varied, of course, if you can directly let the background brother to change the code, it is another matter, most of the case or to the Android end to carry the blame. Here, let’s talk about that.

  • Retrofit2+RXjava encapsulation based on MVP Mode
  • Retrofit2+RXjava package file download based on MVP mode
  • Retrofit2+RXjava package file upload based on MVP mode
  • Common Problems with Retrofit2+RXjava encapsulation based on MVP Mode (iv)
  • MVP mode Retrofit2+RXjava encapsulation breakpoint Download (5)
  • Retrofit2+RXjava Encapsulation based on MVP Mode data preprocessing (6)
  • 【Android architecture 】 Retrofit2+RXjava encapsulation based on MVP mode

The general formula

We will directly copy the json returned by the interface and use the plugin to convert it into an entity class (international convention, not get and set).

public class ShareModel {
 
    private int status;
    private String msg;
    private List<DataBean> data;
    public static class DataBean {
        private String id;
        private String wshare_name;
        private String wshare_head;
        privateString wshare_content; }}Copy the code

Advanced routine

The data returned in the background is in the following format:

{
	"status": 1."msg": "Request successful"."data": []}Copy the code

We’ll define a BaseModel.

public class BaseModel<T> implements Serializable {
    private int status;
    private String msg;
    private T data;
}
Copy the code

If data were a list, a BaseListModel would also be defined, except that the data is list

. Then, you define the interface in ApiServer

 
    @FormUrlEncoded
    @POST("/mapi/index.php? ctl=user&act=userbaseinfo")
    Observable<BaseModel<UserModel>> getUserInfo(@FieldMap Map<String, String> params);
Copy the code

Used in Presenter

    /** * Get user details **@param params
     */
    public void getUserInfo(Map<String, String> params) {
        addDisposable(apiServer.getUserInfo(params), new BaseObserver<BaseModel<UserModel>>(baseView) {

            @Override
            public void onSuccess(BaseModel<UserModel> o) {
                baseView.onGetUserInfoSucc(o.getData());

            }

            @Override
            public void onError(String msg) { baseView.showError(msg); }}); }Copy the code

Then the callback is processed in the activity or fragment. I won’t go into detail on this part, but you can see the previous article.

This may seem fine, but if a background interface returns data in the following format,

{
	"status": 1."error": "Request successful"."data": []}Copy the code

Add an error field to BaseModel and BaseListModel

What if that’s the data?

{
	"code": 1."error": "Request successful"."data": []}Copy the code

Maybe this picture shows how you’re feeling right now

The ultimate routines

Although life is difficult, problems must be solved.

So if we recall that the HTTP request returns an object called ResponseBody, how does that translate into our entity class? Main code here retrofit. AddConverterFactory (GsonConverterFactory. The create ())

Let’s follow him inside

public final class GsonConverterFactory extends Converter.Factory {
  /**
   * Create an instance using a default {@linkGson} instance for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will  use UTF-8. */
  public static GsonConverterFactory create(a) {
    return create(new Gson());
  }

  /**
   * Create an instance using {@code gson} for conversion. Encoding to JSON and
   * decoding from JSON (when no charset is specified by a header) will use UTF-8.
   */
  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
  }

  private final Gson gson;

  private GsonConverterFactory(Gson gson) {
    this.gson = gson;
  }

  @Override
  publicConverter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));return new GsonResponseBodyConverter<>(gson, adapter);
  }

  @Override
  publicConverter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));return newGsonRequestBodyConverter<>(gson, adapter); }}Copy the code

As you can see, the main logic is in GsonResponseBodyConverter

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody.T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);
      if(jsonReader.peek() ! = JsonToken.END_DOCUMENT) {throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally{ value.close(); }}}Copy the code

As you can see, we take the byte stream and then call TypeAdapter’s read method to convert it to our entity class. We won’t go into this principle until we get to it later. Can we do something here? The answer is yes.

First of all, these classes are final and cannot be inherited, but that’s ok, we can copy the code for these classes and rename them

BaseConverterFactory and BaseRequestBodyConverter are consistent with the source code, and only need to modify the class name. Focus on BaseResponseBodyConverter

public class BaseResponseBodyConverter<T> implements Converter<ResponseBody.T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    BaseResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {

        String jsonString = value.string();
        try {
            JSONObject object = new JSONObject(jsonString);

            int code = object.getInt("code");
            if(code ! =1) {
                String msg = object.getString("msg");
                if (TextUtils.isEmpty(msg)) {
                    msg = object.getString("error");
                }
                // Exception handling
                throw new BaseException(msg, code);
            }

            return adapter.fromJson(object.getString("data"));

        } catch (JSONException e) {
            e.printStackTrace();
            // Data parsing exception
            throw new BaseException(BaseException.PARSE_ERROR_MSG, BaseException.PARSE_ERROR);
        } finally{ value.close(); }}}Copy the code

The code that judges can add BaseException on its own, depending on the needs of the project

public class BaseException extends IOException {

    /** * Failed to parse data */
    public static final int PARSE_ERROR = 1001;
    public static final String PARSE_ERROR_MSG = "Failed to parse data";

    /** * Network problem */
    public static final int BAD_NETWORK = 1002;
    public static final String BAD_NETWORK_MSG = "Network problems";
    /** * connection error */
    public static final int CONNECT_ERROR = 1003;
    public static final String CONNECT_ERROR_MSG = "Connection error";
    /** * Connection timed out */
    public static final int CONNECT_TIMEOUT = 1004;
    public static final String CONNECT_TIMEOUT_MSG = "Connection timed out";
    /** * Unknown error */
    public static final int OTHER = 1005;
    public static final String OTHER_MSG = "Unknown error";


    private String errorMsg;
    private int errorCode;


    public String getErrorMsg(a) {
        return errorMsg;
    }

    public int getErrorCode(a) {
        return errorCode;
    }

    public BaseException(String errorMsg, Throwable cause) {
        super(errorMsg, cause);
        this.errorMsg = errorMsg;
    }

    public BaseException(String message, Throwable cause, int errorCode) {
        super(message, cause);
        this.errorCode = errorCode;
        this.errorMsg = message;
    }

    public BaseException(String message, int errorCode) {
        this.errorCode = errorCode;
        this.errorMsg = message; }}Copy the code

Modify the BaseObserver code to handle only successful callbacks in onNext and various exceptions in onError

public abstract class BaseObserver<T> extends DisposableObserver<T> {

    protected BaseView view;

    private boolean isShowDialog;


    public BaseObserver(BaseView view) {
        this.view = view;
    }

    public BaseObserver(BaseView view, boolean isShowDialog) {
        this.view = view;
        this.isShowDialog = isShowDialog;
    }

    @Override
    protected void onStart(a) {
        if(view ! =null&& isShowDialog) { view.showLoading(); }}@Override
    public void onNext(T o) {
        onSuccess(o);
    }

    @Override
    public void onError(Throwable e) {
        if(view ! =null && isShowDialog) {
            view.hideLoading();
        }
        BaseException be = null;

        if(e ! =null) {

            if (e instanceof BaseException) {
                be = (BaseException) e;

                // Callback to the View layer or depending on the project
                if(view ! =null) {
                    view.onErrorCode(new BaseModel(be.getErrorCode(), be.getErrorMsg()));
                } else{ onError(be.getErrorMsg()); }}else {
                if (e instanceof HttpException) {
                    / / HTTP error
                    be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
                } else if (e instanceof ConnectException
                        || e instanceof UnknownHostException) {
                    // Connection error
                    be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
                } else if (e instanceof InterruptedIOException) {
                    // Connection timed out
                    be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
                } else if (e instanceof JsonParseException
                        || e instanceof JSONException
                        || e instanceof ParseException) {
                    // Parsing error
                    be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
                } else {
                    be = newBaseException(BaseException.OTHER_MSG, e, BaseException.OTHER); }}}else {
            be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
        }

        onError(be.getErrorMsg());

    }

    @Override
    public void onComplete(a) {
        if(view ! =null&& isShowDialog) { view.hideLoading(); }}public abstract void onSuccess(T o);

    public abstract void onError(String msg);


}

Copy the code

Add our custom ConverterFactory to ApiRetrofit

 .addConverterFactory(BaseConverterFactory.create())
Copy the code

In this case, ApiServer can be defined like this

 @FormUrlEncoded
    @POST("/mapi/index.php? ctl=user&act=userbaseinfo")
    Observable<UserModel> getUserInfo(@FieldMap Map<String, String> params);
Copy the code

Similarly, presenter can be written like this

  public void getUserInfo(Map<String, String> params) {
        addDisposable(apiServer.getUserInfo(params), new BaseObserver<UserModel>(baseView) {

            @Override
            public void onSuccess(UserModel o) {
                baseView.onGetUserInfoSucc(o);

            }

            @Override
            public void onError(String msg) { baseView.showError(msg); }}); }Copy the code

Is it a lot leaner? Come and try it

Refer to the RxJava2 + Retrofit2 complete guide for uniform status code /Exception handling

Finally, Github

Your recognition is the motivation for me to keep updating my blog. If it is useful, please give me a thumbs-up. Thank you