Get right to the point

The usual JSON data format received from the server looks something like this:

  {
    "code": 1,"message":"Query successful"."detail": {"aa":"123"."bb":"123"."cc":"123"}}Copy the code

So we usually define an entity class to parse the corresponding JSON:

public class Response {
    @SerializedName("code")
    private int code;
    @SerializedName("message")
    private String message;
    @SerializedName("detail") private DetailBean detail; // omit getter and setter methods... public static class DetailBean { @SerializedName("aa")
        private String aa;
        @SerializedName("bb")
        private String bb;
        @SerializedName("cc") private String cc; // omit getter and setter methods... }}Copy the code

The code field represents the status, such as the following values, which may mean different things

  • Code = 1 indicates success, not 1 indicates error
  • Code = -101: Indicates that the token has expired
  • Code = -102: Indicates that the mobile phone number has been registered
  • Etc etc.

If we followed normal Retrofit+RxJava logic, the code would look like this:

//ApiService.java
public interface ApiService {
    String ENDPOINT = Constants.END_POINT;

    @POST("app/api")
    Observable<Response1> request1(@Body Request1 request);

    @POST("app/api")
    Observable<Response2> request2(@Body Request2 request);
    /**
     * Create a new ApiService
     */
    class Factory {
        private Factory() {  }

        public static ApiService createService( ) {
            OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
            builder.readTimeout(10, TimeUnit.SECONDS);
            builder.connectTimeout(9, TimeUnit.SECONDS);

            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
                interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                builder.addInterceptor(interceptor);
            }

            builder.addInterceptor(new HeaderInterceptor());
            OkHttpClient client = builder.build();
            Retrofit retrofit =
                    new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
                            .client(client)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                            .build();
            returnretrofit.create(ApiService.class); }}}Copy the code

When used:

ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Response1>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Response1 response) {
                        int code = response.getCode();
                        switch (code) {
                            case1: / /do something
                                break;
                            case- 101: / /do something
                                break;
                            case- 102: / /do something
                                break;
                            default:
                                break; }}});Copy the code

If you do that for every request, you’re killing yourself, and if one day these values change, say, from -102 to -105, then you don’t have to change everything, which is scary to think about!

The solution

Retrofit retrofit =
                    new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
                            .client(client)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                            .build();
Copy the code

AddConverterFactory (GsonConverterFactory. The create (), the code for the server returned with Gson parses the json data into entities, From here, you can define a GsonConverter to extend the original function

The default GsonConverter is composed of three classes:

  • GsonConverterFactory // GsonConverterFactory class, used to create GsonConverter
  • GsonResponseBodyConverter ResponseBody / / processing
  • GsonRequestBodyConverter // Handles RequestBody

The name of each class is why it is easy to see that, GsonResponseBodyConverter this class must be key and have a look at this class:

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 {
      returnadapter.read(jsonReader); } finally { value.close(); }}}Copy the code

You read that right, just a few lines of code… The convert() method is where you want to extend,

Just add processing code on top of the original logic! = 1 if code! = 1, throws an exception,

First go directly to the code:

//CustomGsonConverterFactory.java public class CustomGsonConverterFactory extends Converter.Factory { private final Gson  gson; private CustomGsonConverterFactory(Gson gson) {if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static CustomGsonConverterFactory create() {
        return create(new Gson());
    }

    public static CustomGsonConverterFactory create(Gson gson) {
        returnnew CustomGsonConverterFactory(gson); } @Override public Converter<ResponseBody, ? > responseBodyConverter(Typetype, Annotation[] annotations,Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
        returnnew CustomGsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<? , RequestBody> requestBodyConverter(Typetype,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
        returnnew CustomGsonRequestBodyConverter<>(gson, adapter); }}Copy the code
//CustomGsonRequestBodyConverter.java
final class CustomGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;

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

    @Override
    public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter, value);
        jsonWriter.close();
        returnRequestBody.create(MEDIA_TYPE, buffer.readByteString()); }}Copy the code
//CustomGsonResponseBodyConverter.java final class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody,  T> { private final Gson gson; private final TypeAdapter<T> adapter; CustomGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { String response = value.string(); HttpStatus httpStatus = gson.fromJson(response, HttpStatus.class);if(httpStatus.isCodeInvalid()) { value.close(); throw new ApiException(httpStatus.getCode(), httpStatus.getMessage()); } MediaType contentType = value.contentType(); Charset charset = contentType ! = null ? contentType.charset(UTF_8) : UTF_8; InputStream inputStream = new ByteArrayInputStream(response.getBytes()); Reader reader = new InputStreamReader(inputStream, charset); JsonReader jsonReader = gson.newJsonReader(reader); try {returnadapter.read(jsonReader); } finally { value.close(); }}}Copy the code

The other two classes is the same as the default, only look at the third class CustomGsonResponseBodyConverter

There are two custom classes: HttpStatus and ApiException.

//HttpStatus.java
public class HttpStatus {
    @SerializedName("code")
    private int mCode;
    @SerializedName("message")
    private String mMessage;

    public int getCode() {
        return mCode;
    }

    public String getMessage() {
        returnmMessage; } /** * WHETHER the API failed to request ** @returnFailure to returntrue, returns successfullyfalse
     */
    public boolean isCodeInvalid() {
        return mCode != Constants.WEB_RESP_CODE_SUCCESS;
    }
}
Copy the code
//ApiException.java public class ApiException extends RuntimeException { private int mErrorCode; public ApiException(int errorCode, String errorMessage) { super(errorMessage); mErrorCode = errorCode; } /** * Check whether the token is invalid ** @returnFailure to returntrueOtherwise returnfalse;
     */
    public boolean isTokenExpried() {
        returnmErrorCode == Constants.TOKEN_EXPRIED; }}Copy the code

It’s easy to understand. Let’s explain a few key lines of code

String response = value.string(); // If code ==1 // if code ==1 // if code ==1 // if code ==1 // if code ==1 // if code ==1 // gson.fromJson(response, HttpStatus.class);if(httpStatus.isCodeInvalid()) { value.close(); // Throw a RuntimeException, which goes to the Subscriber onError() method and throws new ApiException(httpStatus.getCode(), httpStatus.getMessage()); }Copy the code

So there’s a little bit of a ResponseBody pit, if anyone’s ever encountered this exception

java.lang.IllegalStateException: closed
            at com.squareup.okhttp.internal.http.HttpConnection$FixedLengthSource.read(HttpConnection.java:455)
            at okio.Buffer.writeAll(Buffer.java:594)
            at okio.RealBufferedSource.readByteArray(RealBufferedSource.java:87)
            at com.squareup.okhttp.ResponseBody.bytes(ResponseBody.java:56)
            at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:82)
Copy the code

Because you can only read ResponseBody once, this exception is raised if you call Response.body ().string() twice or response.body().charstream () twice. Calling string() followed by charStream() also does not work.

So the usual thing to do is read it once and then save it, and then not read it from the ResponseBody.

Final usage:

Start by creating a BaseSubscriber

//BaseSubscriber.java
public class BaseSubscriber<T> extends Subscriber<T> {
    protected Context mContext;

    public BaseSubscriber(Context context) {
        this.mContext = context;
    }

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(final Throwable e) {
        Log.w("Subscriber onError", e);
        if (e instanceof HttpException) {
            // We had non-2XX http error
            Toast.makeText(mContext, mContext.getString(R.string.server_internal_error), Toast.LENGTH_SHORT).show();
        } else if (e instanceof IOException) {
            // A network or conversion error happened
            Toast.makeText(mContext, mContext.getString(R.string.cannot_connected_server), Toast.LENGTH_SHORT).show();
        } else if (e instanceof ApiException) {
            ApiException exception = (ApiException) e;
            if(exception.istokenexpried ()) {// Handle logic corresponding to token invalidation}else {
                Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        } 
    }

    @Override
    public void onNext(T t) {

    }

}
Copy the code

Request way

ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseSubscriber<Response1>() {
                    @Override
                    public void onCompleted() { super.onCompleted(); } @Override public void onError(Throwable e) { super.onError(e); } @override public void onNext(Response1 response) {super.onnext (response); }});Copy the code

Well, that’s the end of this article