preface

This article focuses on the use of Rxjava in conjunction with Retrofit. For those unfamiliar with Rxjava and Retrofit, check out my previous two introductions

  • Android RxJava: Basic introduction and use
  • Android RxJava series 2: Common extension operators
  • Android Retrofit 2.5.0 uses basic details

The basic use

Define the request interface

public interface GetRequest_Interface {

    @POST("/app/auth/school/list") Observable<School> postSchool(@Body RequestBody route); // Get school by school name}Copy the code

Create a Retrofit interface instance


GetRequest_Interface request = new Retrofit.Builder()
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build().create(GetRequest_Interface.class);

Copy the code

Build request parameters

The request body is a Json string

HashMap<String, Object> map = new HashMap<>();
        map.put(key, value);
        Gson gson=new Gson();
        String strEntity = gson.toJson(map);
        KLog.d("22222222RequestBody"+strEntity);
        RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json; charset=UTF-8"),strEntity);

Copy the code

Start network request

Observable<School> observable = request.postSchool(body); observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<School>() { Override public void onSubscribe(Disposable d) {Override public void onSubscribe(Disposable d) {Override public void onSubscribe(Disposable d) School) {Override public void onError(Throwable e) {Override public void onError(Throwable e) { = 200 ) } @Override public voidonComplete() {// request completes processing, closes request queue, closes dialog, etc.}});Copy the code

At this point,Rxjava and Retrofit combined with the basic use of the end, based on the above can be happy to complete the network request work, is not very convenient and concise.

Of course, for our business, it is impossible to have only one network request, basically everywhere need to go into the network request, and it is not as simple as above. There are other parameters that need to be configured in our business. If every network request is written like this, of course it is possible, but your code is too low and does not conform to programming specifications.

So based on the premise that you will be familiar with the use of the above code, we need to carry out a simple package.

I want to say one more thing about encapsulation

A lot of people think of encapsulation as making code simple enough, logic clear enough, open and closed, and so on, and that’s true, but it depends, and if you’re writing code for a broad audience, which is an open source project, there are a lot of things to be open about.

But if we only use it in our own projects, we must make it clear that the premise of customization is to meet the needs of our business, not too encapsulated. This is why we often need to re-package other people’s open source frameworks. There is no best package, only the most appropriate package.

Packaging paper

Unified Url storage

Public interface API {// Store all BaseUrl String BASE_URL ="http://xx.xxx.xx.225:8080"; // Core API String BASE_SCHOOL_URL ="http://xx.xxx.xx.225:8081"; // school API}Copy the code

Store all request apis

Public interface GetRequest_Interface {/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- all network request API-------------------------------------------------------*/ @POST("/app/auth/captcha") Observable<Phone> postPhone(@Body RequestBody route); // Get the verification code @post ("/app/auth/login") Observable<RegistLogin> postRegist(@Body RequestBody route); // Register}Copy the code

Encapsulates the parameter information needed to process a network request

  • Initialize the OkHttpClient configuration
OkHttpClient client = new okHttpClient.builder ().connectTimeout(DEFAULT_TIME_OUT, Timeunit.seconds) //l connection timeout duration. WriteTimeout (DEFAULT_READ_TIME_OUT, timeunit.seconds) // read/writeTimeout ReadTimeout (DEFAULT_READ_TIME_OUT, TimeUnit. SECONDS) / / read timeout retryOnConnectionFailure (trueAddNetworkInterceptor (tokenRespInterceptor) addNetworkInterceptor(tokenRespInterceptor) //.authenticator(authenticator) // Authorize authentication.build ();Copy the code

OkHttp3 interceptors are used here. If you are not familiar with them, you may want to know about them

  • Add a common request header uniformly
 Interceptor tokenInterceptor = new Interceptor() {// Global interceptor,  @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); / / get Request. The original Request Builder requestBuilder = originalRequest. NewBuilder () / / a new Request. AddHeader ("Accept"."application/json")
                        .addHeader("Content-Type"."application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
                returnchain.proceed(requestBuilder.build()); // request againCopy the code
  • The Token is dynamically added globally
 Interceptor tokenInterceptor = new Interceptor() {// Global interceptor, add token field to request header, Override public okHttp3.Response Intercept (Chain Chain) throws IOException {Request originalRequest = chain.request(); / / get Request. The original Request Builder requestBuilder = originalRequest. NewBuilder () / / a new Request. AddHeader ("Accept"."application/json")
                        .addHeader("Content-Type"."application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
//                Log.e("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -",originalRequest.body().toString()); Request tokenRequest = null; // Request with tokenif (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
                    return chain.proceed(requestBuilder.build());
                }

                tokenRequest = requestBuilder
                        .header("Authorization"."Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
                        .build();
                returnchain.proceed(tokenRequest); }};Copy the code

Here, Tencent’s MMKV framework is used to store the tokens locally. Since we did not get the tokens at the beginning, we need to add them dynamically

  • The token automatically checks whether the token is expired

In network interaction, the Token issued by the server is time-limited and generally short. After the expiration of the validity period, a request for a new Token needs to be made. This process cannot be perceived by users, so users need to refresh and request for a new Token without being aware of it.

  Interceptor tokenRespInterceptor = new Interceptor() {// Intercept the return body to determine whether the token is expired. Override Public Response Intercept (Chain Chain) throws IOException {Request Request = chain.request(); Response response = chain.proceed(request); // KLog.d( response.body().string());if (isTokenExpired(response)){
                    KLog.d( "Automatically refresh Token and then request data again"); String newToken = getNewToken();if(newToken ! Request newRequest = chain.request().newBuilder().header()"Authorization"."Bearer "+ newToken) .build(); // request againreturnchain.proceed(newRequest); }}return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build(); }};Copy the code

Here needs to be judged according to the expiration rules agreed by the server, here is a simple demonstration

/** * Check whether Token is invalid ** @param Response * @return
     */
    private boolean isTokenExpired(Response response) {
        try {
            body = response.body().string();
            JSONObject object = new JSONObject(body);
            message = object.getString("Message");
            code = object.getInt("Code");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if ("Token is expired".equals(message)&& code == 1) {
            return true;
        }
        return false;
    }

Copy the code

Get a new Token

/** * Synchronizes the request to obtain the latest Token ** @return
     */
    private String getNewToken() {// Through the token interface, GetRequest_Interface Request = new retrofit.builder ().baseurl (api.base_url) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build().create(GetRequest_Interface.class); // KLog.e(Remember.getString("refresh_token",null));
        RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
        Call<RefreshToken> call = request.postRefreshToken(body);
        try {
            response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        KLog.e(response.body().getCode()+response.body().getMessage());

        if (response.code() == 200 &&response.body().getCode() ==0){
            newToken = response.body().getData().getToken();
            KLog.e("-- -- -- -- -- -- -- -- -- -- -- -- -- -- --"+newToken);
            App.mmkv.encode(BaseConfig.TOKEN,newToken);
            App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
            App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
        }

        return newToken;
    }

Copy the code
  • Initialize Retrofit
  retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

Copy the code

At this point, the parameters related to the network request are basically configured

Encapsulate the above configuration steps

/** * Created by Darryrzhong ** * unified Retrofit portal */ public class RetrofitHelper {private static final int DEFAULT_TIME_OUT = 5; Private static final int DEFAULT_READ_TIME_OUT = 10; private static final int DEFAULT_READ_TIME_OUT = 10; private final Retrofit retrofit; private String body; private retrofit2.Response<RefreshToken> response; private String newToken; private String message; private int code; privateRetrofitHelper(){


        Interceptor tokenInterceptor = new Interceptor() {// Global interceptor, add token field to request header, Override public okHttp3.Response Intercept (Chain Chain) throws IOException {Request originalRequest = chain.request(); / / get Request. The original Request Builder requestBuilder = originalRequest. NewBuilder () / / a new Request. AddHeader ("Accept"."application/json")
                        .addHeader("Content-Type"."application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
//                Log.e("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -",originalRequest.body().toString()); Request tokenRequest = null; // Request with tokenif (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
                    return chain.proceed(requestBuilder.build());
                }

                tokenRequest = requestBuilder
                        .header("Authorization"."Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
                        .build();
                returnchain.proceed(tokenRequest); }}; Interceptor tokenRespInterceptor = newInterceptor() {// Intercept the return body to determine whether the token is expired. Override Public Response Intercept (Chain Chain) throws IOException {Request Request = chain.request(); Response response = chain.proceed(request); // KLog.d( response.body().string());if (isTokenExpired(response)){
                    KLog.d( "Automatically refresh Token and then request data again"); String newToken = getNewToken();if(newToken ! Request newRequest = chain.request().newBuilder().header()"Authorization"."Bearer "+ newToken) .build(); // request againreturnchain.proceed(newRequest); }}return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build(); }}; OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(DEFAULT_TIME_OUT, Timeunit.seconds) //l connection timeout duration. WriteTimeout (DEFAULT_READ_TIME_OUT, timeunit.seconds) // read/writeTimeout ReadTimeout (DEFAULT_READ_TIME_OUT, TimeUnit. SECONDS) / / read timeout retryOnConnectionFailure (trueAddNetworkInterceptor (tokenRespInterceptor) addNetworkInterceptor(tokenRespInterceptor) //.authenticator(authenticator) // Authorize authentication.build (); retrofit = new Retrofit.Builder() .client(client) .baseUrl(API.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();  } /** * synchronizes the request mode to obtain the latest Token ** @return
     */
    private String getNewToken() {// Through the token interface, GetRequest_Interface Request = new retrofit.builder ().baseurl (api.base_url) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build().create(GetRequest_Interface.class); RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
        Call<RefreshToken> call = request.postRefreshToken(body);
        try {
            response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        KLog.e(response.body().getCode()+response.body().getMessage());

        if (response.code() == 200 &&response.body().getCode() ==0){
            newToken = response.body().getData().getToken();
            KLog.e("-- -- -- -- -- -- -- -- -- -- -- -- -- -- --"+newToken);
            App.mmkv.encode(BaseConfig.TOKEN,newToken);
            App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
            App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
        }

        returnnewToken; } /** * Check whether Token is invalid ** @param Response * @return
     */
    private boolean isTokenExpired(Response response) {
        try {
            body = response.body().string();
            JSONObject object = new JSONObject(body);
            message = object.getString("Message");
            code = object.getInt("Code");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if ("Token is expired".equals(message)&& code == 1) {
            return true;
        }
        return false; } private static class SingletonHolder{ private static final RetrofitHelper INSTANCE = new RetrofitHelper(); } /** * Get RetrofitServiceManager * @return
     */
    public static RetrofitHelper getInstance() {returnSingletonHolder.INSTANCE; } /** * get the class of Service * @param Service * @param <T> * @return
     */
    public  <T> T create(Class<T> service){
        returnretrofit.create(service); }}Copy the code

If there are multiple baseurls in the business, you can simply write a method to expose it.

A BaseObserver that processes the results of a request uniformly

  • Start by creating a ResponseCallBack callback for the result of the request
public interface ResponseCallBack<T> {
    void onSuccess(T t);

    void onFault(String errorMsg);
}

Copy the code
  • Create an initialization callback ProgressListener at the start of the request
public interface ProgressListener {
    void startProgress();

    void cancelProgress();
}


Copy the code
  • Create a BaseObserver for unified processing of results

Create a BaseObserver for business processing in a callback

public class BaseObserver<T> implements Observer<T> { private ResponseCallBack responseCallBack; private ProgressListener progressListener; private Disposable disposable; public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){ this.responseCallBack = responseCallBack; this.progressListener = progressListener; }}Copy the code

Initialization at the start of the request is done in the onSubscribe () method

  @Override
    public void onSubscribe(Disposable d) {
         this.disposable = d;
         if (progressListener != null){
             progressListener.startProgress();
         }
    }

Copy the code

The request success business is handled in the onNext () method

@Override
    public void onNext(T t) {
         responseCallBack.onSuccess(t);
    }

Copy the code

Handle request failure messages uniformly in the onError () method

 @Override
    public void onError(Throwable e) {
        KLog.d(e.getMessage());
        try {

            if(e instanceof SocketTimeoutException) {responsecallBack.onfault ("Request timed out, please try again later.");
            } else if(e instanceof ConnectException) {responsecallBack.onfault ("Network connection timed out. Please check network status.");
            } else if(e instanceof SSLHandshakeException) {responsecallBack.onfault ("Security Certificate exception");
            } else if(e instanceof HttpException) {int code = ((HttpException) e).code();if (code == 504) {
                    responseCallBack.onFault("Network exception, please check your network status.");
                } else if (code == 404) {
                    responseCallBack.onFault("Requested address not found");
                } else {
                    responseCallBack.onFault("Request failed"); }}else if(e instanceof UnknownHostException) {responsecallback.onfault ("Domain name resolution failed");
            } else {
                responseCallBack.onFault("error:" + e.getMessage());
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        } finally {
            Log.e("OnSuccessAndFaultSub"."error:" + e.getMessage());
            if(disposable ! =null && ! Disposable. IsDisposed () {// Event completion cancel subscription disposable. Dispose (); }if(progressListener! =null){ progressListener.cancelProgress(); }}}Copy the code

Handle the business after the request completes successfully in onComplete ()

 @Override
    public void onComplete() {
        if(disposable ! =null && ! Disposable. IsDisposed () {// Event completion cancel subscription disposable. Dispose (); }if (progressListener!=null){
            progressListener.cancelProgress();
        }
    }

Copy the code

The code is as follows:

/**
 * Created by darryrzhong
 * 
 */


public  class BaseObserver<T> implements Observer<T> {

 private ResponseCallBack responseCallBack;
 private ProgressListener progressListener;
 private Disposable disposable;

 public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){
     this.responseCallBack = responseCallBack;
     this.progressListener = progressListener;
 }

    @Override
    public void onSubscribe(Disposable d) {
         this.disposable = d;
         if(progressListener ! = null){ progressListener.startProgress(); } } @Override public void onNext(T t) { responseCallBack.onSuccess(t); } @Override public void onError(Throwable e) { KLog.d(e.getMessage()); try {if(e instanceof SocketTimeoutException) {responsecallBack.onfault ("Request timed out, please try again later.");
            } else if(e instanceof ConnectException) {responsecallBack.onfault ("Network connection timed out. Please check network status.");
            } else if(e instanceof SSLHandshakeException) {responsecallBack.onfault ("Security Certificate exception");
            } else if(e instanceof HttpException) {int code = ((HttpException) e).code();if (code == 504) {
                    responseCallBack.onFault("Network exception, please check your network status.");
                } else if (code == 404) {
                    responseCallBack.onFault("Requested address not found");
                } else {
                    responseCallBack.onFault("Request failed"); }}else if(e instanceof UnknownHostException) {responsecallback.onfault ("Domain name resolution failed");
            } else {
                responseCallBack.onFault("error:" + e.getMessage());
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        } finally {
            Log.e("OnSuccessAndFaultSub"."error:" + e.getMessage());
            if(disposable ! =null && ! Disposable. IsDisposed () {// Event completion cancel subscription disposable. Dispose (); }if(progressListener! =null){ progressListener.cancelProgress(); } } } @Override public voidonComplete() {
        if(disposable ! =null && ! Disposable. IsDisposed () {// Event completion cancel subscription disposable. Dispose (); }if(progressListener! =null){ progressListener.cancelProgress(); }}}Copy the code

At this point, the BaseObserver for unified processing results is wrapped

Others (request parameter pass, return JSON)

This varies depending on how the server receives data. To learn more about how to pass parameters, read Retrofit’s annotations for yourself. This is just about passing Json data to the server.

  • First let’s look at the standard Json data format
Return body: {"Code": 0."Data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC*********"."refresh_token": "c9ced011-***************************"."expire_in": 1560330991,
        "student_id": 33},"Message": "Login successful"} Request body: {"phone":"13145214436"."id":"12345"}

Copy the code

How to pass the request body

Let’s start by treating the outermost {} JSON object as a map object, so that you know how to convert it. Yes, that’s what you think.

 HashMap<String, Object> map = new HashMap<>();
        map.put("phone"."13145214436");
        map.put("id"."12345");

Copy the code

The map object is then converted into a JSON string and passed to the server

        Gson gson=new Gson();
        String strEntity = gson.toJson(map);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json; charset=UTF-8"),strEntity);

Copy the code

The same is true for more complex request bodies

{
  "school_id": 1,"student_id": 23."start_time":"The 2019-05-10 15:04:05"."end_time":"The 2019-06-10 15:04:05"."points": [{"longitude": 0."latitude": 0}, {"longitude": 0."latitude": 0}, {"longitude": 0."latitude": 0}, {"longitude": 0."latitude": 0}]}Copy the code

We have a json array in the body of the request, and we have a nested JSON object in the body of the request, so again, think of the JSON array as a list.

Use the sample

Here is a simple demonstration of the login service

GetRequest_Interface request = RetrofitHelper.getInstance().create(GetRequest_Interface.class); // Request entry HashMap<String,Object> params = new HashMap(); params.put("phone",phone);
            params.put("id",id); RequestBody body = BaseUtils.convertJson(params); Observable<RegistLogin> observable= request.postRegist(body); observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new BaseObserver<RegistLogin>(new ResponseCallBack<RegistLogin>() { @Override public void onSuccess(RegistLogin registLogin) @override public void onFault(String errorMsg) {baseutils. showmsg (mContext,errorMsg); } }, newProgressListener() {
                          @Override
                          public void startProgress() {
                              dialog = BaseUtils.showSpotsDialog(mContext,"Logging in");
                              dialog.show();
                          }

                          @Override
                          public void cancelProgress() { dialog.dismiss(); }}));Copy the code

In this way, the code is not clear and concise, code quality significantly improved a level

At this point,Rxjava and Retrofit combined use and encapsulation is basically complete, again, there is no perfect encapsulation, only the most suitable for your business encapsulation, so, if necessary, please customize your own business, here is only the idea.

Welcome to darryrzhong, more dry goods waiting for you to get yo.

A little red heart, please! Because your encouragement is the biggest power that I write!

Please pay attention to more wonderful articles

  • Personal blog: Darryrzhong
  • The Denver nuggets
  • Jane’s book
  • SegmentFault
  • Moocs Notebook