LoadingTransformer:利用RxJava优雅显示隐藏加载对话框

958 阅读2分钟

从事Android开发过程中,我们经常要在网络请求的时候显示“加载中”或“请求中”的对话框,然后在请求结束时显示对应状态或隐藏对话框,而手动关闭对话框时又要取消加载。相信类似需求应该经常碰到,有时候一个项目中会有好几个这样的地方,如果每个地方都要实现一遍类似的功能,那也是一件很郁闷的事。本文就展示一种通过增加一行代码来处理这种需求的工具--LoadingTransformer

1. 示例

首先来看一个示例:

Observable
        .fromCallable(() -> {
            Log.d(TAG, "test loading: ");
            Thread.sleep(3000);              //模拟长时间请求
            return 1;
        })
        .subscribeOn(Schedulers.io())
        .compose(new LoadingTransformer<>(context, "加载中..."))
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(i -> {
            //处理结果
        }, throwable -> {
            //处理异常
        });

这里用sleep来模拟长时间请求,通过添加一行compose(new LoadingTransformer<>(context, "加载中..."))来实现加载过程中显示对话框,当加载完成的时候对话框消失。

在来看第二个示例:

Observable
        .fromCallable(() -> {
            Log.d(TAG, "test loading: ");
            Thread.sleep(3000);         //模拟长时间请求
            if (new Random().nextBoolean()) {//随机出错,模拟异常情况
                throw new RuntimeException("something wrong!");
            }
            return 1;
        })
        .subscribeOn(Schedulers.io())
        .compose(new LoadingTransformer<>(view.getContext(), "加载中...", "加载失败", "加载结束"))
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(i -> {
            //处理结果
        }, throwable -> {
            //处理异常
        });

这里我们给LoadingTransformer提供了三个参数,分别表示加载中的文案、加载失败的文案以及加载结束的文案,这样对话框就会在对应的状态显示对应的内容。

2. LoadingTransformer的实现

import android.content.Context;

import androidx.appcompat.app.AlertDialog;

import org.reactivestreams.Publisher;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.CompletableSource;
import io.reactivex.rxjava3.core.CompletableTransformer;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.FlowableTransformer;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.MaybeSource;
import io.reactivex.rxjava3.core.MaybeTransformer;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableSource;
import io.reactivex.rxjava3.core.ObservableTransformer;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.SingleSource;
import io.reactivex.rxjava3.core.SingleTransformer;
import io.reactivex.rxjava3.disposables.Disposable;

public class LoadingTransformer<T> implements ObservableTransformer<T, T>,
        SingleTransformer<T, T>,
        MaybeTransformer<T, T>,
        FlowableTransformer<T, T>,
        CompletableTransformer {  //实现了RxJava的各种Transformer,方便使用

    private final AlertDialog dialog; //处于演示目的,使用AlertDialog,实际中用对应自定义弹窗替代
    private String errorMsg;
    private String completeMsg;

    public LoadingTransformer(Context context, String s) {
        dialog = new AlertDialog.Builder(context).setMessage(s).setCancelable(true).create();
    }

    public LoadingTransformer(Context context, String s, String errorMsg) {
        dialog = new AlertDialog.Builder(context).setMessage(s).setCancelable(true).create();
        this.errorMsg = errorMsg;
    }

    public LoadingTransformer(Context context, String s, String errorMsg, String completeMsg) {
        dialog = new AlertDialog.Builder(context).setMessage(s).setCancelable(true).create();
        this.errorMsg = errorMsg;
        this.completeMsg = completeMsg;
    }

    private void onComplete() {
        if (Objects.nonNull(completeMsg)) {
            dialog.setMessage(completeMsg);
        } else {
            if (dialog.isShowing()) {
                dialog.dismiss();
            }
        }
    }

    private void onError(Throwable t) {
        if (Objects.nonNull(errorMsg)) {
            dialog.setMessage(errorMsg);
        } else {
            if (dialog.isShowing()) {
                dialog.dismiss();
            }
        }
    }

    @Override
    public @NonNull CompletableSource apply(@NonNull Completable upstream) {
        return upstream
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(d -> dialog.setOnDismissListener(dialogInterface -> d.dispose()))
                .doOnSubscribe((Disposable d) -> dialog.show())
                .doOnComplete(this::onComplete)
                .doOnError(this::onError);
    }

    @Override
    public @NonNull MaybeSource<T> apply(@NonNull Maybe<T> upstream) {
        return upstream
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(d -> dialog.setOnDismissListener(dialogInterface -> d.dispose()))
                .doOnSubscribe(d -> dialog.show())
                .doOnComplete(this::onComplete)
                .doOnError(this::onError);
    }

    @Override
    public @NonNull ObservableSource<T> apply(@NonNull Observable<T> upstream) {
        return upstream
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(d -> dialog.setOnDismissListener(dialogInterface -> d.dispose()))
                .doOnSubscribe(d -> dialog.show())
                .doOnComplete(this::onComplete)
                .doOnError(this::onError);
    }

    @Override
    public @NonNull SingleSource<T> apply(@NonNull Single<T> upstream) {
        return upstream
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(d -> dialog.setOnDismissListener(dialogInterface -> d.dispose()))
                .doOnSubscribe(d -> dialog.show())
                .doOnSuccess(t -> onComplete())
                .doOnError(this::onError);
    }

    @Override
    public @NonNull Publisher<T> apply(@NonNull Flowable<T> upstream) {
        return upstream
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(subscription -> dialog.setOnDismissListener(dialogInterface -> subscription.cancel()))
                .doOnSubscribe(subscription -> dialog.show())
                .doOnComplete(this::onComplete)
                .doOnError(this::onError);
    }
}

3. 注意

  1. 调用Dialog的setOnDismissListener方法时注意避免重复设置导致覆盖的情况,只设置一次就好。
  2. 如果要根据网络返回的结果(比如状态码)来提示异常,可以在doOnSuccessdoOnNext中对结果进行判断并对应信息。