Android Encapsulates Rxjava2+Retrofit Perfectly

I learned the basic usage of Rxjava and Retrofit last year, but I haven't used them in actual projects.Starting a new project this year, we decisively introduced RxJava and Retrofit into the new project.This article describes the author's encapsulation of Retrofit in the project.
Let's first see how Retrofit works after encapsulation.

RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("Successful request, number of sisters" + response.size());
                    }
                });

Yes, it's such a concise chain call that shows loading animations and incorporates Retrofit lifecycle management.
You need to add the dependent libraries used in the Gradle file in the module project before you start

    compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"
    compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version"
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle"
    //compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle"
    compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"

To facilitate modification of dependent library versions, we add dependencies in the way "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version". Therefore, you need to add the following to the project build.gradle file:

ext {
    supportLibVersion = '25.1.0'
    butterknifeVersion = '8.5.1'
    rxjava2Version = '2.0.8'
    retrofit2Version = '2.2.0'
    rxlifecycle='2.1.0'
    gsonVersion = '2.8.0'
}

This encapsulation will be analyzed in detail in the following subsections:

  • Base Class BasicResponse for Server Response Data
  • Build the tool class IdeaApi to initialize Retrofit
  • Get real response data from GsonConverterFactory
  • Encapsulate DefaultObserver to process server response
  • Processing Loading
  • Manage Retrofit life cycle
  • How to use encapsulation
  • Summary

Base class BasicResponse for server response data.

Assume that the Json data returned by the server is in the following format:

{
 "code": 200,
 "message": "Success",
 "content": {
    ...
    }
}

Build our BasicResponse based on the Json data format (the field content in the BasicResponse needs to be determined based on the data returned by your server).The code is as follows:

public class BasicResponse<T> {

    private int code;
    private String message;
    private T content;
    ...Omit here get,set Method.

2. Build the tool class IdeaApi to initialize Retrofit.

This class RetrofitUtils to get an instance of ApiService.The code is as follows:

public class IdeaApi {
    public static <T> T getApiService(Class<T> cls,String baseUrl) {
        Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build();
        return retrofit.create(cls);
    }
}

RetrofitUtils is used to build Retrofit.Builder and configure OkHttp in the following ways:

  1. Set up a log interceptor to intercept the json data returned by the server.Retrofit converts requests to json data directly into entity classes, but sometimes we need to look at json data. Retrofit does not provide the ability to get json data directly.So we need to customize a log interceptor to intercept json data and enter it into the console.

  2. Set Http request header.Add a request header interceptor to OkHttp to configure the request header information.Request header data can also be added uniformly for interfaces.For example, add a user name, password (or token) to the request header uniformly.Subsequent request headers for each interface carry user name, password (or token) data, avoiding adding individual data for each interface.

  3. Configure the cache for OkHttp.Cache processing can also be implemented with interceptors.This includes controlling the maximum lifetime of the cache and controlling the expiration time of the cache.

  4. With https, we can also process certificate and server checks here.

  5. Add GsonConverterFactory for Retrofit.This is an important part, which will be explained in detail later.

The RetrofitUtils code is as follows:

public class RetrofitUtils {
    public static OkHttpClient.Builder getOkHttpClientBuilder() {

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                try {
                    LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    LogUtils.e("OKHttp-----", message);
                }
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb

        return new OkHttpClient.Builder()
                .readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new HttpHeaderInterceptor())
                .addNetworkInterceptor(new HttpCacheInterceptor())
               // .sslSocketFactory (SslContextFactory.getSSLSocketFactoryForTwoWay ())// https authentication If you want to use https and for custom certificates you can remove these two lines of comment and make your own certificate.
               // .hostnameVerifier(new SafeHostnameVerifier())
                .cache(cache);
    }

    public static Retrofit.Builder getRetrofitBuilder(String baseUrl) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
        OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build();
        return new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(baseUrl);
    }
}

3. Obtain real response data through GsonConverterFactory

In the first section, we constructed a BasicResponse for the server response data, which consists of three fields: code, message, and content.Where code is the error code returned for the server.The code value for success will be agreed upon beforehand with the server, such as 200 for request success.However, errors usually occur when requesting server data.Examples include incorrect passwords and request parameters when a user logs in.In this case, the server will return the corresponding error code according to the error condition.Generally speaking, we only care about content data when success is code 200.For codes other than 200, we just need to give the corresponding Toast prompt.In fact, only content data with code 2000 is useful to us.So we can consider filtering out the code and message and returning only the content of the content in callbacks that request success.

In this case, we need to customize GsonConverterFactory.We can copy three related classes of GsonConverterFactory directly from Retrofit's source code to make modifications.

The final part is to modify the conversion method in GsonResponseBodyConverter.In this method, you get the server response data and determine if the code is 200.If so, it gets the content and returns it. If not, it throws a corresponding custom exception here.Exceptions are then handled uniformly in Observer.The GsonResponseBodyConverter code is as follows:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {

    private final TypeAdapter<T> adapter;

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

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream());
            if (response.getCode()==200) {
            return response.getResults();
            } else {
                // API-specific errors, handled in the onError method of the corresponding DefaultObserver
                throw new ServerResponseException(response.getCode(), response.getMessage());
            }
        } finally {
            value.close();
        }
        return null;
    }
}

4. Build DefaultObserver to process server responses.

In the previous section, we talked about some possible situations when requesting the server, such as password errors and parameter errors. The server returned us the corresponding error code, and we threw out the corresponding custom exception based on the error code.In addition, there may be some anomalies when we initiate a network request.For example, there is no network, the request timed out, or the server returned data but there was a data parsing exception during parsing.In such cases, we also need to take a unified approach.Then we need to customize a DefaultObserver class to inherit Observer and override the corresponding method.
The two most important methods in this class are onNext and onError.

1. Callback to onNext method if the server returns data successfully.So we can define an abstract method, onSuccess(T response), in DefaultObserver that overrides the onSuccess method when calling the network.

2. If any exceptions occur during the request to the server, they will be called back into the onError method.Including the exception we threw ourselves in the previous section, we will call back to onError.So our main focus is on handling onError.In onError, we can give the corresponding Toast prompt based on the exception information.

The code for the DefaultObserver class is as follows:

public abstract class DefaultObserver<T> implements Observer<T> {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(T response) {
        onSuccess(response);
        onFinish();
    }

    @Override
    public void onError(Throwable e) {
        LogUtils.e("Retrofit", e.getMessage());
        if (e instanceof HttpException) {     //   HTTP Error
            onException(ExceptionReason.BAD_NETWORK);
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {   //   Connection Error
            onException(ExceptionReason.CONNECT_ERROR);
        } else if (e instanceof InterruptedIOException) {   //  connection timed out
            onException(ExceptionReason.CONNECT_TIMEOUT);
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {   //  Parsing error
            onException(ExceptionReason.PARSE_ERROR);
        }else if(e instanceof ServerResponseException){
            onFail(e.getMessage());
        } else {
            onException(ExceptionReason.UNKNOWN_ERROR);
        }
        onFinish();
    }

    @Override
    public void onComplete() {
    }

    /**
     * Request succeeded
     *
     * @param response Data returned by the server
     */
    abstract public void onSuccess(T response);

    /**
     * The server returned data but the response code was not 200
     *
     */
    public void onFail(String message) {
        ToastUtils.show(message);
    }

    public void onFinish(){}

    /**
     * Request Exception
     *
     * @param reason
     */
    public void onException(ExceptionReason reason) {
        switch (reason) {
            case CONNECT_ERROR:
                ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT);
                break;

            case CONNECT_TIMEOUT:
                ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT);
                break;

            case BAD_NETWORK:
                ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT);
                break;

            case PARSE_ERROR:
                ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT);
                break;

            case UNKNOWN_ERROR:
            default:
                ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT);
                break;
        }
    }

    /**
     * Request Network Failure Reason
     */
    public enum ExceptionReason {
        /**
         * Failed to parse data
         */
        PARSE_ERROR,
        /**
         * Network problems
         */
        BAD_NETWORK,
        /**
         * Connection Error
         */
        CONNECT_ERROR,
        /**
         * connection timed out
         */
        CONNECT_TIMEOUT,
        /**
         * unknown error
         */
        UNKNOWN_ERROR,
    }
}

5. Processing Loading

For Loading we can do a very elegant job with the RxJava compose operator.First, define a ProgressUtils tool class, then use ObservableTransformer from RxJava to make a transformation to process Loading.To display Loading, just add.Compose (ProgressUtils. < T > applyProgressBar (this)).

The ProgressUtils code is as follows:

public class ProgressUtils {
    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity, String msg) {
        final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
        final DialogUtils dialogUtils = new DialogUtils();
        dialogUtils.showProgress(activityWeakReference.get());
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {

                    }
                }).doOnTerminate(new Action() {
                    @Override
                    public void run() throws Exception {
                        Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }
                    }
                }).doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {
                        /*Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }*/
                    }
                });
            }
        };
    }

    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity) {
        return applyProgressBar(activity, "");
    }
}

The re-encapsulation of RxJava and Retrofit is now almost complete.But one important thing we can't ignore is the lifecycle of network requests.We'll go into more detail in the next section.

VI. Managing Retrofit Life Cycle

When the activity is destroyed, the network request should also be terminated.Otherwise, it may cause a memory leak.The performance of App will be greatly affected!Therefore, the management of Retrofit life cycle is also an important point.Here we use RxLifecycle To manage the life cycle of Retrofit.The flow of its use is as follows:

1. Add dependencies to the gradel as follows:

compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'

2. Let our BaseActivity inherit RxAppCompatActivity.
The code is as follows:

public abstract class BaseActivity extends RxAppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        init(savedInstanceState);
    }
    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    protected abstract @LayoutRes int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);
}

Similarly, our project's BaseFragment inherits RxFragment (note the use of RxFragment under the Inherited V4 package), as follows:

public abstract class BaseFragment extends RxFragment {

    public View rootView;
    public LayoutInflater inflater;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        this.inflater = inflater;
        if (rootView == null) {
            rootView = inflater.inflate(this.getLayoutId(), container, false);
            init(savedInstanceState);
        }
        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (parent != null) {
            parent.removeView(rootView);
        }
        return rootView;
    }

    protected abstract int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);

    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }
}

3. Use the compose operator to manage the Retrofit life cycle:

myObservable
            .compose(bindToLifecycle())
            .subscribe();

//perhaps

myObservable
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
    .subscribe();

Detailed usage of RxLifecycle can be referred to RxLifecycle Official Website

7. How to use packaging

The previous sections explain how RxJava can be re-encapsulated, and the encapsulated code can be placed in the Library module of our project.So how should we use it in the app module after encapsulation?

1. Define an interface to store the API for our project

public interface IdeaApiService {
    /**
     * The generic T for this interface server response data BasicResponse should be List<MeiZi>
     * BasicResponse<List<MeiZi>>
     * @return BasicResponse<List<MeiZi>>
     */
    @Headers("Cache-Control: public, max-age=10")//Set cache cache time to 100s
    @GET("welfare/10/1")
    Observable<List<MeiZi>> getMezi();

    /**
     * Logon interface is false and cannot return data
     * @return
     */
    @POST("login.do")
    Observable<LoginResponse> login(@Body LoginRequest request);

    /**
     * Refreshing the token interface as a fake interface does not return data
     * @return
     */
    @POST("refresh_token.do")
    Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request);

    @Multipart
    @POST("upload/uploadFile.do")
    Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList);
}

2. Define a RetrofitHelper class to obtain an instance of IdeaApiService through IdeaApi.

public class RetrofitHelper {
    private static IdeaApiService mIdeaApiService;

    public static IdeaApiService getApiService(){
        return mIdeaApiService;
    }
    static {
       mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL);
    }
}

3. Initiate a network request in an Activity or Fragment

    /**
     * Get request
     * @param view
     */
    public void getData(View view) {
        RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("Successful request, number of sisters" + response.size());
                    }
                });
    }

8. Summary

This article focuses on the secondary encapsulation of Rxjava and Retrofit.The above content is also the author's reference to a variety of data after a long period of changes and optimization.However, due to my limited ability, some inappropriateness can not be avoided.Please also include more.In addition, there may be many inelegant aspects of the article when it is publishing the public number of Guo Shen, such as the processing of response data and the processing of Loading.After the submission was pushed, I received a lot of suggestions from small partners, so I also consulted your opinions and optimized them. Thank you here.Finally, if you have questions, you are welcome to leave a comment on the article.

Keywords: Android Retrofit network JSON OkHttp

Added by celebx on Tue, 06 Aug 2019 04:13:25 +0300