One, foreword

Most of the popular web request frameworks in Android today are built with Retrofit plus RxJava, and there are far fewer examples of request frameworks built with ViewModel + LiveData + Retrofit + RxJava. And this article is to take these four as the basic components, introduce how to encapsulate step by step to achieve their own network request framework (the example of this article is not only a network request framework, but also in the introduction of application architecture mode), hope to help you

The functions or features realized at present include the following:

1. Network request results are delivered based on observer mode, and the callback operation is bound to the LIFECYCLE of the UI layer to avoid memory leaks

2. StartLoading during data loading and dismissLoading after data loading are automatically called, and the specific implementation is encapsulated in the base class. Of course, subclasses can also implement their own specific implementations. For example, in the example provided in this article, the BaseActivity implements the ProgressDialog loading dialog, and the child Activity can implement other pop-ups of its own

3. When the network request result is not successful (network request failure or service request failure), the default operation is to use Toast to remind the cause of the failure, and to support customized implementation failure operations

4. The logical operation is separated from the UI layer, and message-driven UI changes are implemented based on the observer pattern. Provides the ability to manipulate UI changes in the ViewModel, including UI actions such as Activity/Fragment pop-up dialogs, Toast messages, and FinishActivities. But the ViewModel does not hold references to activities/fragments, and is message-driven, avoiding memory leaks

Click here to view the source code:ReactiveHttp

Encapsulate BaseViewModel and BaseActivity

ViewModel and LiveData are both components of the Android Jetpack architecture. The ViewModel is designed to store and manage UI-related data so that it can hold data in the event of interface destruction (such as screen rotation), while the LiveData attached to the ViewModel is a data holding class that holds values that can be observed, following the lifecycle of the application component. Data update notifications are received only when the component’s life cycle is active

Being message-driven, you naturally need an Event class that abstracts message types

/ * * * the author: leavesC * time: 2018/9/30 therefore * description: * GitHub:https://github.com/leavesC * /
public class BaseEvent {

    private int action;

    public BaseEvent(int action) {
        this.action = action;
    }

    public int getAction(a) {
        returnaction; }}public class BaseActionEvent extends BaseEvent {

    public static final int SHOW_LOADING_DIALOG = 1;

    public static final int DISMISS_LOADING_DIALOG = 2;

    public static final int SHOW_TOAST = 3;

    public static final int FINISH = 4;

    public static final int FINISH_WITH_RESULT_OK = 5;

    private String message;

    public BaseActionEvent(int action) {
        super(action);
    }

    public String getMessage(a) {
        return message;
    }

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

BaseActionEvent is the Model used to pass the Action to the View layer, where the ViewModel triggers the corresponding Action by passing different message types to the View layer. Therefore, BaseViewModel needs to provide a default implementation to subclasses

public interface IViewModelAction {

    void startLoading(a);

    void startLoading(String message);

    void dismissLoading(a);

    void showToast(String message);

    void finish(a);

    void finishWithResultOk(a);

    MutableLiveData<BaseActionEvent> getActionLiveData(a);

}
Copy the code
/ * * * the author: leavesC * time: 2018/9/30 22:24 * description: * GitHub:https://github.com/leavesC * /
public class BaseViewModel extends ViewModel implements IViewModelAction {

    private MutableLiveData<BaseActionEvent> actionLiveData;

    protected LifecycleOwner lifecycleOwner;

    public BaseViewModel(a) {
        actionLiveData = new MutableLiveData<>();
    }

    @Override
    public void startLoading(a) {
        startLoading(null);
    }

    @Override
    public void startLoading(String message) {
        BaseActionEvent baseActionEvent = new BaseActionEvent(BaseActionEvent.SHOW_LOADING_DIALOG);
        baseActionEvent.setMessage(message);
        actionLiveData.setValue(baseActionEvent);
    }

    @Override
    public void dismissLoading(a) {
        actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.DISMISS_LOADING_DIALOG));
    }

    @Override
    public void showToast(String message) {
        BaseActionEvent baseActionEvent = new BaseActionEvent(BaseActionEvent.SHOW_TOAST);
        baseActionEvent.setMessage(message);
        actionLiveData.setValue(baseActionEvent);
    }

    @Override
    public void finish(a) {
        actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.FINISH));
    }

    @Override
    public void finishWithResultOk(a) {
        actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.FINISH_WITH_RESULT_OK));
    }

    @Override
    public MutableLiveData<BaseActionEvent> getActionLiveData(a) {
        return actionLiveData;
    }

    void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        this.lifecycleOwner = lifecycleOwner; }}Copy the code

That completes the concrete implementation of the BaseViewModel as the message sender, followed by the BaseActivity/BaseFragment as the message receiver

BaseActivity starts loading when a network request starts loading and dismissLoading when it ends by listening for data changes in actionLiveData in BaseViewModel

Generally, one Activity corresponds to one ViewModel, and in rare cases, multiple viewModels, so initViewModel() is declared as an abstract method, and initViewModelList() returns NULL by default

/ * * * the author: leavesC * time: 2017/11/29 21:04 * description: * GitHub:https://github.com/leavesC * /
@SuppressLint("Registered")
public abstract class BaseActivity extends AppCompatActivity {

    private ProgressDialog loadingDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViewModelEvent();
    }

    protected abstract ViewModel initViewModel(a);

    protected List<ViewModel> initViewModelList(a) {
        return null;
    }

    private void initViewModelEvent(a) {
        List<ViewModel> viewModelList = initViewModelList();
        if(viewModelList ! =null && viewModelList.size() > 0) {
            observeEvent(viewModelList);
        } else {
            ViewModel viewModel = initViewModel();
            if(viewModel ! =null) {
                List<ViewModel> modelList = newArrayList<>(); modelList.add(viewModel); observeEvent(modelList); }}}private void observeEvent(List<ViewModel> viewModelList) {
        for (ViewModel viewModel : viewModelList) {
            if (viewModel instanceof IViewModelAction) {
                IViewModelAction viewModelAction = (IViewModelAction) viewModel;
                viewModelAction.getActionLiveData().observe(this, baseActionEvent -> {
                    if(baseActionEvent ! =null) {
                        switch (baseActionEvent.getAction()) {
                            case BaseActionEvent.SHOW_LOADING_DIALOG: {
                                startLoading(baseActionEvent.getMessage());
                                break;
                            }
                            case BaseActionEvent.DISMISS_LOADING_DIALOG: {
                                dismissLoading();
                                break;
                            }
                            case BaseActionEvent.SHOW_TOAST: {
                                showToast(baseActionEvent.getMessage());
                                break;
                            }
                            case BaseActionEvent.FINISH: {
                                finish();
                                break;
                            }
                            case BaseActionEvent.FINISH_WITH_RESULT_OK: {
                                setResult(RESULT_OK);
                                finish();
                                break; }}}}); }}}@Override
    protected void onDestroy(a) {
        super.onDestroy();
        dismissLoading();
    }

    protected void startLoading(a) {
        startLoading(null);
    }

    protected void startLoading(String message) {
        if (loadingDialog == null) {
            loadingDialog = new ProgressDialog(this);
            loadingDialog.setCancelable(false);
            loadingDialog.setCanceledOnTouchOutside(false);
        }
        loadingDialog.setTitle(message);
        loadingDialog.show();
    }

    protected void dismissLoading(a) {
        if(loadingDialog ! =null&& loadingDialog.isShowing()) { loadingDialog.dismiss(); }}protected void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    protected void finishWithResultOk(a) {
        setResult(RESULT_OK);
        finish();
    }

    protected BaseActivity getContext(a) {
        return BaseActivity.this;
    }

    protected void startActivity(Class cl) {
        startActivity(new Intent(this, cl));
    }

    public void startActivityForResult(Class cl, int requestCode) {
        startActivityForResult(new Intent(this, cl), requestCode);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    protected boolean isFinishingOrDestroyed(a) {
        returnisFinishing() || isDestroyed(); }}Copy the code

Encapsulate Retrofit with RxJava

As mentioned in the introduction, the framework implements actions when a request fails by default (Toast prompts the reason for the failure) and also supports a custom callback interface. Therefore, you need two callback interfaces, one containing only the callback interface if the request succeeds, and one containing more than one callback interface if the request fails

/ * * * the author: leavesC * time: 2018/10/27 20:53 * description: * GitHub:https://github.com/leavesC * /
public interface RequestCallback<T> {

    void onSuccess(T t);

}

public interface RequestMultiplyCallback<T> extends RequestCallback<T> {

    void onFail(BaseException e);

}
Copy the code

In addition, you need to customize BaseException in order to throw detailed failure information when a network request succeeds but a business logic request fails (for example, request parameters are missing, Token invalidation, etc.)

public class BaseException extends RuntimeException {

    private int errorCode = HttpCode.CODE_UNKNOWN;

    public BaseException(a) {}public BaseException(int errorCode, String errorMessage) {
        super(errorMessage);
        this.errorCode = errorCode;
    }

    public int getErrorCode(a) {
        returnerrorCode; }}Copy the code

Implement a concrete exception class

public class ParamterInvalidException extends BaseException {

    public ParamterInvalidException(a) {
        super(HttpCode.CODE_PARAMETER_INVALID, "Wrong parameters"); }}public class TokenInvalidException extends BaseException {

    public TokenInvalidException(a) {
        super(HttpCode.CODE_TOKEN_INVALID, "Token failure"); }}...Copy the code

To improve performance, Retrofit is typically designed as a singleton pattern. In case there may be multiple BaseUrl in your application (as in the Demo provided with this article), Map is used here to store multiple Retrofit instances

/ * * * the author: leavesC * time: 2018/10/26 and * description: * GitHub:https://github.com/leavesC * /
public class RetrofitManagement {

    private static final long READ_TIMEOUT = 6000;

    private static final long WRITE_TIMEOUT = 6000;

    private static final long CONNECT_TIMEOUT = 6000;

    private final Map<String, Object> serviceMap = new ConcurrentHashMap<>();

    private RetrofitManagement(a) {}public static RetrofitManagement getInstance(a) {
        return RetrofitHolder.retrofitManagement;
    }

    private static class RetrofitHolder {
        private static final RetrofitManagement retrofitManagement = new RetrofitManagement();
    }

    private Retrofit createRetrofit(String url) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
                .writeTimeout(WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(new HttpInterceptor())
                .addInterceptor(new HeaderInterceptor())
                .addInterceptor(new FilterInterceptor())
                .retryOnConnectionFailure(true);
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(httpLoggingInterceptor);
            builder.addInterceptor(new ChuckInterceptor(ContextHolder.getContext()));
        }
        OkHttpClient client = builder.build();
        return new Retrofit.Builder()
                .client(client)
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    <T> ObservableTransformer<BaseResponseBody<T>, T> applySchedulers() {
        return observable -> observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(result -> {
                    switch (result.getCode()) {
                        case HttpCode.CODE_SUCCESS: {
                            return createData(result.getData());
                        }
                        case HttpCode.CODE_TOKEN_INVALID: {
                            throw new TokenInvalidException();
                        }
                        case HttpCode.CODE_ACCOUNT_INVALID: {
                            throw new AccountInvalidException();
                        }
                        default: {
                            throw newServerResultException(result.getCode(), result.getMsg()); }}}); }private <T> Observable<T> createData(T t) {
        return Observable.create(new ObservableOnSubscribe<T>() {
            @Override
            public void subscribe(ObservableEmitter<T> emitter) {
                try {
                    emitter.onNext(t);
                    emitter.onComplete();
                } catch(Exception e) { emitter.onError(e); }}}); } <T>T getService(Class<T> clz) {
        return getService(clz, HttpConfig.BASE_URL_WEATHER);
    }

    <T> T getService(Class<T> clz, String host) {
        T value;
        if (serviceMap.containsKey(host)) {
            Object obj = serviceMap.get(host);
            if (obj == null) {
                value = createRetrofit(host).create(clz);
                serviceMap.put(host, value);
            } else{ value = (T) obj; }}else {
            value = createRetrofit(host).create(clz);
            serviceMap.put(host, value);
        }
        returnvalue; }}Copy the code

A custom Observer is also required to perform custom callbacks to data request results

/ * * * the author: leavesC * time: 2018/10/27 20:52 * description: * GitHub:https://github.com/leavesC * /
public class BaseSubscriber<T> extends DisposableObserver<T> {

    private BaseViewModel baseViewModel;

    private RequestCallback<T> requestCallback;

    public BaseSubscriber(BaseViewModel baseViewModel) {
        this.baseViewModel = baseViewModel;
    }

    BaseSubscriber(BaseViewModel baseViewModel, RequestCallback<T> requestCallback) {
        this.baseViewModel = baseViewModel;
        this.requestCallback = requestCallback;
    }

    @Override
    public void onNext(T t) {
        if(requestCallback ! =null) { requestCallback.onSuccess(t); }}@Override
    public void onError(Throwable e) {
        e.printStackTrace();
        if (requestCallback instanceof RequestMultiplyCallback) {
            RequestMultiplyCallback callback = (RequestMultiplyCallback) requestCallback;
            if (e instanceof BaseException) {
                callback.onFail((BaseException) e);
            } else {
                callback.onFail(newBaseException(HttpCode.CODE_UNKNOWN, e.getMessage())); }}else {
            if (baseViewModel == null) {
                Toast.makeText(ContextHolder.getContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
            } else{ baseViewModel.showToast(e.getMessage()); }}}@Override
    public void onComplete(a) {}}Copy the code

BaseRemoteDataSource and BaseRepo

RequestCallback, RetrofitManagement, and BaseSubscriber are all separate entities that need a linker to link them together. The linker’s implementation class is the BaseRemoteDataSource

In this case, the BaseRemoteDataSource is positioned as an interface implementor, that is, the request interfaces are actually called in the RemoteDataSource and the timing of loading ejection and destruction is controlled by RxJava

In general, the BaseRemoteDataSource implementation class declares interfaces with associated logic. For example, you can declare a LoginDataSource for a login module and a SettingsDataSource for a Settings module

/ * * * the author: leavesC * time: 2018/10/27 7:42 * description: * GitHub:https://github.com/leavesC * /
public abstract class BaseRemoteDataSource {

    private CompositeDisposable compositeDisposable;

    private BaseViewModel baseViewModel;

    public BaseRemoteDataSource(BaseViewModel baseViewModel) {
        this.compositeDisposable = new CompositeDisposable();
        this.baseViewModel = baseViewModel;
    }

    protected <T> T getService(Class<T> clz) {
        return RetrofitManagement.getInstance().getService(clz);
    }

    protected <T> T getService(Class<T> clz, String host) {
        return RetrofitManagement.getInstance().getService(clz, host);
    }

    private <T> ObservableTransformer<BaseResponseBody<T>, T> applySchedulers() {
        return RetrofitManagement.getInstance().applySchedulers();
    }

    protected <T> void execute(Observable observable, RequestCallback<T> callback) {
        execute(observable, new BaseSubscriber<>(baseViewModel, callback), true);
    }

    protected <T> void execute(Observable observable, RequestMultiplyCallback<T> callback) {
        execute(observable, new BaseSubscriber<>(baseViewModel, callback), true);
    }

    public void executeWithoutDismiss(Observable observable, Observer observer) {
        execute(observable, observer, false);
    }

    private void execute(Observable observable, Observer observer, boolean isDismiss) {
        Disposable disposable = (Disposable) observable
                .throttleFirst(500. TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .compose(applySchedulers()) .compose(isDismiss ? loadingTransformer() : loadingTransformerWithoutDismiss()) .subscribeWith(observer); addDisposable(disposable); }private void addDisposable(Disposable disposable) {
        compositeDisposable.add(disposable);
    }

    public void dispose(a) {
        if (!compositeDisposable.isDisposed()) {
            compositeDisposable.dispose();
        }
    }

    private void startLoading(a) {
        if(baseViewModel ! =null) { baseViewModel.startLoading(); }}private void dismissLoading(a) {
        if(baseViewModel ! =null) { baseViewModel.dismissLoading(); }}private <T> ObservableTransformer<T, T> loadingTransformer(a) {
        return observable -> observable
                .subscribeOn(AndroidSchedulers.mainThread())
                .unsubscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(disposable -> startLoading())
                .doFinally(this::dismissLoading);
    }

    private <T> ObservableTransformer<T, T> loadingTransformerWithoutDismiss(a) {
        returnobservable -> observable .subscribeOn(AndroidSchedulers.mainThread()) .unsubscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(disposable -> startLoading()); }}Copy the code

In addition to the BaseRemoteDataSource, you need a BaseRepo. BaseRepo is positioned as an interface scheduler that holds the BaseRemoteDataSource instance and relits the ViewModel’s interface call requests, sharing some of the data processing logic with BaseRepo

/ * * * the author: leavesC * time: 2018/10/27 as * description: * GitHub:https://github.com/leavesC * /
public class BaseRepo<T> {

    protected T remoteDataSource;

    public BaseRepo(T remoteDataSource) {
        this.remoteDataSource = remoteDataSource; }}Copy the code

In this way, the ViewModel does not care about the actual call implementation of the interface, so it is convenient to change the implementation of the BaseRemoteDataSource in the future, and puts part of the data processing logic in BaseRepo, which is conducive to logic reuse

V. Practical operation (1) – Request weather data

The logic implementation and positioning of some basic components have been described above. This section takes an interface that requests weather data as an example to introduce how to specifically implement the overall process of a network request

The first is to declare the interface

public interface ApiService {

    @Headers({HttpConfig.HTTP_REQUEST_TYPE_KEY + ":" + HttpConfig.HTTP_REQUEST_WEATHER})
    @GET("onebox/weather/query")
    Observable<BaseResponseBody<Weather>> queryWeather(@Query("cityname") String cityName);

}
Copy the code

The header information is added to indicate the request type of the interface. Since the interfaces in this demo use different baseUrl and request keys, the header is declared to dynamically specify the request parameters for the interface, which requires Retrofit’s interceptor

public class FilterInterceptor implements Interceptor {

    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request originalRequest = chain.request();
        HttpUrl.Builder httpBuilder = originalRequest.url().newBuilder();
        Headers headers = originalRequest.headers();
        if(headers ! =null && headers.size() > 0) {
            String requestType = headers.get(HttpConfig.HTTP_REQUEST_TYPE_KEY);
            if(! TextUtils.isEmpty(requestType)) {switch (requestType) {
                    case HttpConfig.HTTP_REQUEST_WEATHER: {
                        httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_WEATHER);
                        break;
                    }
                    case HttpConfig.HTTP_REQUEST_QR_CODE: {
                        httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_QR_CODE);
                        break;
                    }
                    case HttpConfig.HTTP_REQUEST_NEWS: {
                        httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_NEWS);
                        break;
                    }
                }
            }
        }
        Request.Builder requestBuilder = originalRequest.newBuilder()
                .removeHeader(HttpConfig.HTTP_REQUEST_TYPE_KEY)
                .url(httpBuilder.build());
        returnchain.proceed(requestBuilder.build()); }}Copy the code

Declare the implementation class WeatherDataSource for BaseRemoteDataSource

public class WeatherDataSource extends BaseRemoteDataSource implements IWeatherDataSource {

    public WeatherDataSource(BaseViewModel baseViewModel) {
        super(baseViewModel);
    }

    @Override
    public void queryWeather(String cityName, RequestCallback<Weather> responseCallback) { execute(getService(ApiService.class).queryWeather(cityName), responseCallback); }}Copy the code

Declare BaseRepo’s implementation class WeatherRepo

public class WeatherRepo extends BaseRepo<IWeatherDataSource> {

    public WeatherRepo(IWeatherDataSource remoteDataSource) {
        super(remoteDataSource);
    }

    public MutableLiveData<Weather> queryWeather(String cityName) {
        MutableLiveData<Weather> weatherMutableLiveData = new MutableLiveData<>();
        remoteDataSource.queryWeather(cityName, new RequestCallback<Weather>() {
            @Override
            public void onSuccess(Weather weather) { weatherMutableLiveData.setValue(weather); }});returnweatherMutableLiveData; }}Copy the code

You also need a WeatherViewModel. The View layer triggers weatherLiveData to update data by calling queryWeather() on success. The View layer listens for weatherLiveData beforehand. And can receive the latest data immediately when the data is updated

public class WeatherViewModel extends BaseViewModel {

    private MutableLiveData<Weather> weatherLiveData;

    private WeatherRepo weatherRepo;

    public WeatherViewModel(a) {
        weatherLiveData = new MutableLiveData<>();
        weatherRepo = new WeatherRepo(new WeatherDataSource(this));
    }

    public void queryWeather(String cityName) {
        weatherRepo.queryWeather(cityName).observe(lifecycleOwner, new Observer<Weather>() {
            @Override
            public void onChanged(@Nullable Weather weather) { weatherLiveData.setValue(weather); }}); }public MutableLiveData<Weather> getWeatherLiveData(a) {
        returnweatherLiveData; }}Copy the code

Print the result of the request for the outgoing interface in QueryWeatherActivity

public class QueryWeatherActivity extends BaseActivity {

    private static final String TAG = "QueryWeatherActivity";

    private WeatherViewModel weatherViewModel;

    private EditText et_cityName;

    private TextView tv_weather;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_query_weather);
        et_cityName = findViewById(R.id.et_cityName);
        tv_weather = findViewById(R.id.tv_weather);
    }

    @Override
    protected ViewModel initViewModel(a) {
        weatherViewModel = LViewModelProviders.of(this, WeatherViewModel.class);
        weatherViewModel.getWeatherLiveData().observe(this.this::handlerWeather);
        return weatherViewModel;
    }

    private void handlerWeather(Weather weather) {
        StringBuilder result = new StringBuilder();
        for (Weather.InnerWeather.NearestWeather nearestWeather : weather.getData().getWeather()) {
            result.append("\n\n").append(new Gson().toJson(nearestWeather));
        }
        tv_weather.setText(result.toString());
    }

    public void queryWeather(View view) {
        tv_weather.setText(null); weatherViewModel.queryWeather(et_cityName.getText().toString()); }}Copy the code

One might find it a bit cumbersome to create three implementation classes (WeatherDataSource, WeatherRepo, WeatherViewModel) and one interface (IQrCodeDataSource) in order to request an interface. But this is a corollary of wanting to separate responsibilities and separate logic from THE UI. The WeatherDataSource is used to implement the actual invocation of the interface, only requesting the data and passing the result of the request. The WeatherRepo is used to mask the WeatherViewModel’s perception of the WeatherDataSource and takes over some of the data processing logic. The WeatherViewModel is used to isolate logic from the UI and ensure that data is not lost due to page reconstruction. In this way, the Activity can try to be solely responsible for the presentation of data without the need for data processing logic

Six, practical operation (2) – request to generate two-dimensional code

Here’s another example of a QR code that generates the specified content

public class QrCodeDataSource extends BaseRemoteDataSource implements IQrCodeDataSource {

    public QrCodeDataSource(BaseViewModel baseViewModel) {
        super(baseViewModel);
    }

    @Override
    public void createQrCode(String text, int width, RequestCallback<QrCode> callback) { execute(getService(ApiService.class, HttpConfig.BASE_URL_QR_CODE).createQrCode(text, width), callback); }}Copy the code

All the interface asks for is a Base64-encoded string, and what the outside world wants is a Bitmap that can be used directly, so the data can be converted in the Repo before being passed to the outside world

public class QrCodeRepo extends BaseRepo<IQrCodeDataSource> {

    public QrCodeRepo(IQrCodeDataSource remoteDataSource) {
        super(remoteDataSource);
    }

    public MutableLiveData<QrCode> createQrCode(String text, int width) {
        MutableLiveData<QrCode> liveData = new MutableLiveData<>();
        remoteDataSource.createQrCode(text, width, new RequestCallback<QrCode>() {
            @SuppressLint("CheckResult")
            @Override
            public void onSuccess(QrCode qrCode) {
                Observable.create(new ObservableOnSubscribe<Bitmap>() {
                    @Override
                    public void subscribe(@NonNull ObservableEmitter<Bitmap> emitter) throws Exception {
                        Bitmap bitmap = base64ToBitmap(qrCode.getBase64_image());
                        emitter.onNext(bitmap);
                        emitter.onComplete();
                    }
                }).subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Consumer<Bitmap>() {
                            @Override
                            public void accept(@NonNull Bitmap bitmap) throws Exception { qrCode.setBitmap(bitmap); liveData.setValue(qrCode); }}); }});return liveData;
    }

    private static Bitmap base64ToBitmap(String base64String) {
        byte[] decode = Base64.decode(base64String, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(decode, 0, decode.length); }}Copy the code
public class QrCodeViewModel extends BaseViewModel {

    private MutableLiveData<QrCode> qrCodeLiveData;

    private QrCodeRepo qrCodeRepo;

    public QrCodeViewModel(a) {
        qrCodeLiveData = new MutableLiveData<>();
        qrCodeRepo = new QrCodeRepo(new QrCodeDataSource(this));
    }

    public void createQrCode(String text, int width) {
        qrCodeRepo.createQrCode(text, width).observe(lifecycleOwner, new Observer<QrCode>() {
            @Override
            public void onChanged(@Nullable QrCode qrCode) { qrCodeLiveData.setValue(qrCode); }}); }public MutableLiveData<QrCode> getQrCodeLiveData(a) {
        returnqrCodeLiveData; }}Copy the code

Practice (3) – Example of request failure

As mentioned in the preface, the network framework encapsulated in this paper, when the network request result is not successful (network request failure or service request failure), the default operation is to use Toast to remind the failure cause, and also supports customized implementation failure operations. Here’s what happens when a request fails

Two non-existent interfaces need to be declared

public interface ApiService {

    @GET("leavesC/test1")
    Observable<BaseResponseBody<String>> test1();

    @GET("leavesC/test2")
    Observable<BaseResponseBody<String>> test2();

}

Copy the code
public class FailExampleDataSource extends BaseRemoteDataSource implements IFailExampleDataSource {

    public FailExampleDataSource(BaseViewModel baseViewModel) {
        super(baseViewModel);
    }

    @Override
    public void test1(RequestCallback<String> callback) {
        execute(getService(ApiService.class).test1(), callback);
    }

    @Override
    public void test2(RequestCallback<String> callback) { execute(getService(ApiService.class).test2(), callback); }}Copy the code
public class FailExampleRepo extends BaseRepo<IFailExampleDataSource> {

    public FailExampleRepo(IFailExampleDataSource remoteDataSource) {
        super(remoteDataSource);
    }

    public MutableLiveData<String> test1(a) {
        MutableLiveData<String> newsPackMutableLiveData = new MutableLiveData<>();
        remoteDataSource.test1(new RequestCallback<String>() {
            @Override
            public void onSuccess(String newsPack) { newsPackMutableLiveData.setValue(newsPack); }});return newsPackMutableLiveData;
    }

    public void test2(RequestMultiplyCallback<String> callback) { remoteDataSource.test2(callback); }}Copy the code

The test1() method uses the default failure callback of the base class, i.e., Toast a failure message. The test2() method, on the other hand, customizes the callback if the request fails

public class FailExampleViewModel extends BaseViewModel {

    private MutableLiveData<String> test1LiveData = new MutableLiveData<>();

    private MutableLiveData<String> test2LiveData = new MutableLiveData<>();

    private FailExampleRepo failExampleRepo = new FailExampleRepo(new FailExampleDataSource(this));

    public void test1(a) {
        failExampleRepo.test1().observe(lifecycleOwner, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) { test1LiveData.setValue(s); }}); }public void test2(a) {
        failExampleRepo.test2(new RequestMultiplyCallback<String>() {
            @Override
            public void onFail(BaseException e) {
                showToast("Test2 method request failed:" + e.getMessage());
                finish();
            }

            @Override
            public void onSuccess(String s) { test2LiveData.setValue(s); }}); }}Copy the code

Viii. Concluding remarks

This is the general framework of the whole request framework, and it has also passed the test of the actual project. It runs well at present, but there may be some unreasonable places in it. We welcome your correction and feedback, and welcome STAR if you think it will help you

Click here to view the source code:ReactiveHttp