重构 google 的 NetworkBoundResource 类以使用 RxJava 而不是 LiveData

2024-05-11

谷歌的android架构组件教程here https://developer.android.com/topic/libraries/architecture/guide.html有一部分解释了如何抽象通过网络获取数据的逻辑。在其中,他们使用 LiveData 创建一个名为 NetworkBoundResource 的抽象类,以创建反应流作为所有反应网络请求的基础。

public abstract class NetworkBoundResource<ResultType, RequestType> {
private final AppExecutors appExecutors;

private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();

@MainThread
NetworkBoundResource(AppExecutors appExecutors) {
    this.appExecutors = appExecutors;
    result.setValue(Resource.loading(null));
    LiveData<ResultType> dbSource = loadFromDb();
    result.addSource(dbSource, data -> {
        result.removeSource(dbSource);
        if (shouldFetch()) {
            fetchFromNetwork(dbSource);
        } else {
            result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
        }
    });
}

private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
    LiveData<ApiResponse<RequestType>> apiResponse = createCall();
    // we re-attach dbSource as a new source, it will dispatch its latest value quickly
    result.addSource(dbSource, newData -> result.setValue(Resource.loading(newData)));
    result.addSource(apiResponse, response -> {
        result.removeSource(apiResponse);
        result.removeSource(dbSource);
        //noinspection ConstantConditions
        if (response.isSuccessful()) {
            appExecutors.diskIO().execute(() -> {
                saveCallResult(processResponse(response));
                appExecutors.mainThread().execute(() ->
                        // we specially request a new live data,
                        // otherwise we will get immediately last cached value,
                        // which may not be updated with latest results received from network.
                        result.addSource(loadFromDb(),
                                newData -> result.setValue(Resource.success(newData)))
                );
            });
        } else {
            onFetchFailed();
            result.addSource(dbSource,
                    newData -> result.setValue(Resource.error(response.errorMessage, newData)));
        }
    });
}

protected void onFetchFailed() {
}

public LiveData<Resource<ResultType>> asLiveData() {
    return result;
}

@WorkerThread
protected RequestType processResponse(ApiResponse<RequestType> response) {
    return response.body;
}

@WorkerThread
protected abstract void saveCallResult(@NonNull RequestType item);

@MainThread
protected abstract boolean shouldFetch();

@NonNull
@MainThread
protected abstract LiveData<ResultType> loadFromDb();

@NonNull
@MainThread
protected abstract LiveData<ApiResponse<RequestType>> createCall();
}

据我了解,该类的逻辑是:

a) 创建一个名为“result”的MediatorLiveData作为主要返回对象,并将其初始值设置为Resource.loading(null)

b) 从 Android Room db 获取数据作为 dbSource LiveData 并将其作为源 LiveData 添加到“结果”

c) 在 dbSource LiveData 第一次发射时,从“result”中删除 dbSource LiveData 并调用“shouldFetchFromNetwork()”,这将

  1. 如果为 TRUE,则调用“fetchDataFromNetwork(dbSource)”,它通过“createCall()”创建网络调用,返回封装为 ApiResponse 对象的响应的 LiveData
  2. 将 dbSource LiveData 添加回“结果”,并将发出的值设置为 Resource.loading(data)
  3. 将 apiResponce LiveData 添加到“结果”,并在第一次发射时删除 dbSource 和 apiResponce LiveDatas
  4. 如果 apiResponse 成功,则调用“saveCallResult(processResponse(response))”并将 dbSource LiveData 添加回“result”,并将发出的值设置为 Resource.success(newData)
  5. 如果 apiResponse 失败,则调用“onFetchFailed()”并将 dbSource LiveData 添加回“result”,并将发出的值设置为 Resource.error(response.errorMessage, newData))
  6. 如果为 FALSE,只需将 dbSource LiveData 添加到“结果”并将发出的值设置为 Resource.success(newData)

鉴于此逻辑是正确的解释,我尝试重构此类以使用 RxJava Observables 而不是 LiveData。这是我成功重构的尝试(我删除了最初的 Resource.loading(null),因为我认为这是多余的)。

public abstract class NetworkBoundResource<ResultType, RequestType> {

private Observable<Resource<ResultType>> result;

@MainThread
NetworkBoundResource() {
    Observable<Resource<ResultType>> source;
    if (shouldFetch()) {
        source = createCall()
                .subscribeOn(Schedulers.io())
                .doOnNext(apiResponse -> saveCallResult(processResponse(apiResponse)))
                .flatMap(apiResponse -> loadFromDb().toObservable().map(Resource::success))
                .doOnError(t -> onFetchFailed())
                .onErrorResumeNext(t -> {
                    return loadFromDb()
                            .toObservable()
                            .map(data -> Resource.error(t.getMessage(), data))

                })
                .observeOn(AndroidSchedulers.mainThread());
    } else {
        source = loadFromDb()
                .toObservable()
                .map(Resource::success);
    }

    result = Observable.concat(
            loadFromDb()
                    .toObservable()
                    .map(Resource::loading)
                    .take(1),
            source
    );
}

public Observable<Resource<ResultType>> asObservable() {return result;}

protected void onFetchFailed() {}

@WorkerThread
protected RequestType processResponse(ApiResponse<RequestType> response) {return response.body;}

@WorkerThread
protected abstract void saveCallResult(@NonNull RequestType item);

@MainThread
protected abstract boolean shouldFetch();

@NonNull
@MainThread
protected abstract Flowable<ResultType> loadFromDb();

@NonNull
@MainThread
protected abstract Observable<ApiResponse<RequestType>> createCall();
}

由于我是 RxJava 新手,我的问题是我是否正确重构为 RxJava 并维护与此类的 LiveData 版本相同的逻辑?


public abstract class ApiRepositorySource<RawResponse extends BaseResponse, ResultType> {

    // result is a Flowable because Room Database only returns Flowables
    // Retrofit response will also be folded into the stream as a Flowable
    private Flowable<ApiResource<ResultType>> result; 
    private AppDatabase appDatabase;

    @MainThread
    ApiRepositorySource(AppDatabase appDatabase) {
        this.appDatabase = appDatabase;
        Flowable<ApiResource<ResultType>> source;
        if (shouldFetch()) {
            source = createCall()
                .doOnNext(this::saveCallResult)
                .flatMap(apiResponse -> loadFromDb().toObservable().map(ApiResource::success))
                .doOnError(this::onFetchFailed)
                .onErrorResumeNext(t -> {
                    return loadFromDb()
                            .toObservable()
                            .map(data -> {
                                ApiResource apiResource;

                                if (t instanceof HttpException && ((HttpException) t).code() >= 400 && ((HttpException) t).code() < 500) {
                                    apiResource = ApiResource.invalid(t.getMessage(), data);
                                } else {
                                    apiResource = ApiResource.error(t.getMessage(), data);
                                }

                                return apiResource;
                            });
                })
                .toFlowable(BackpressureStrategy.LATEST);
        } else {
            source = loadFromDb()
                    .subscribeOn(Schedulers.io())
                    .map(ApiResource::success);
        }

        result = Flowable.concat(initLoadDb()
                            .map(ApiResource::loading)
                            .take(1),
                            source)
                .subscribeOn(Schedulers.io());
    }

    public Observable<ApiResource<ResultType>> asObservable() {
        return result.toObservable();
    }

    @SuppressWarnings("WeakerAccess")
    protected void onFetchFailed(Throwable t) {
        Timber.e(t);
    }

    @WorkerThread
    protected void saveCallResult(@NonNull RawResult resultType) {
        resultType.saveResponseToDb(appDatabase);
    }

    @MainThread
    protected abstract boolean shouldFetch();

    @NonNull
    @MainThread
    protected abstract Flowable<ResultType> loadFromDb();

    @NonNull
    @MainThread
    protected abstract Observable<RawResult> createCall();

    @NonNull
    @MainThread
    protected Flowable<ResultType> initLoadDb() {
        return loadFromDb();
    }
}

所以这是我经过多次迭代后决定使用的。它目前正在生产中,并且对我的应用程序运行良好。以下是一些要点:

  1. 创建一个BaseResponse界面

        public interface BaseResponse {
             void saveResponseToDb(AppDatabase appDatabase);
        }
    

    并在所有 api 响应对象类中实现它。这样做意味着您不必在每个 ApiResource 中实现 save_to_database 逻辑,如果您愿意,您可以将其默认为响应的实现。

  2. 为了简单起见,我选择在 onErrorResumeNext 块中处理 Retrofit 错误响应,但我建议您创建一个可以容纳所有这些逻辑的 Transformer 类。在这种情况下,我添加了一个额外的StatusApiResources 的枚举值称为INVALID400 级响应。

  3. 您可能会想使用 LiveData 的 Reactive Streams 架构组件库

    implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"并向该类添加一个名为

        public LiveData<ApiResource<ResultType>> asLiveData {
             return LiveDataReactiveStreams.fromPublisher(result);
        }
    

    理论上,这可以完美地工作,因为我们的 ViewModel 不必将 Observable 发射转换为 LiveData 发射或在视图中实现 Observable 的生命周期逻辑。不幸的是,这个流会在每次配置更改时重建,因为它会在任何调用的 onDestroy 中处理 LiveData(无论 isFinishing 是 true 还是 false)。因此,我们必须管理该流的生命周期,这违背了最初使用它的目的,或者每次设备旋转时都会重复调用。

这是一个例子UserRepository创建 ApiNetworkResource 的实例

@Singleton
public class UserRepository {

    private final RetrofitApi retrofitApi;
    private final AppDatabase appDatabase;

    @Inject
    UserRepository(RetrofitApi retrofitApi, AppDatabase appDatabase) {
        this.retrofitApi = retrofitApi;
        this.appDatabase = appDatabase;
    }

    public Observable<ApiResource<User>> getUser(long userId) {
        return new ApiRepositorySource<UserResponse, User>(appDatabase) {

            @Override
            protected boolean shouldFetch() {
                return true;
            }

            @NonNull
            @Override
            protected Flowable<User> loadFromDb() {
                return appDatabase.userDao().getUserFlowable(userId);
            }

            @NonNull
            @Override
            protected Observable<UserResponse> createCall() {
                return retrofitApi.getUserById(userId);
            }
        }.asObservable();
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重构 google 的 NetworkBoundResource 类以使用 RxJava 而不是 LiveData 的相关文章

  • Android:传递给 AESObfuscator 的随机 SALT 字节是否需要保持不变?

    我正在 Android 应用程序中实现许可 并且需要将一个 20 字节的数组传递到 AESObfuscator 然后再传递给 ServerManagedPolicy 对象 这个数组可以在每次运行代码时随机生成 还是必须硬编码 现在我正在随机
  • 谷歌分析崩溃报告仅显示堆栈跟踪的第一行

    我的应用程序使用 Google Analytics 来跟踪异常和崩溃 除其他外 我使用这个函数来获取堆栈跟踪 public static void sendErrorReportViaGoogleAnalytics Exception e
  • 如何将点击侦听器添加到 Android/Java Textview 中的字符串中?

    我想要完成的是大多数 Twitter 应用程序中的标准操作 在文本视图中 文本字符串中的单词前面可能有 提及或 主题标签 并且它们实际上能够添加点击侦听器这个词启动了另一项活动 有谁知道这是如何实现的 下面我附上了一张示例照片 显示了我想要
  • Java J文件选择器

    我希望能够控制外观JFileChooser 我特别想保存如何JFileChooser上次显示时显示 我想保存它是否在详细信息 列表视图中使用以及列表被排序的列 例如 大小或修改日期 我知道有很多关于JFileChooser但我一直没能找到我
  • 跨平台 IAP

    我已在 iOS 和 Android 中集成了应用内购买 自动续订订阅 两者都工作正常 我对使用跨平台验证 IAP 有一定的疑问 例如 当多个用户登录一个应用程序时 某一特定应用程序的订阅如何运作 例如 如果用户 A 有标准套餐并且从应用程序
  • 我可以举一个使用 runOnUiThread 显示 toast 的示例吗?

    我搜索了很多地方 但找不到 runOnUiThread 实现的完整工作示例 我尝试了很多 但出现了很多错误 我只想显示线程中的吐司 这是最终的完整代码 感谢所有回复的人 import android app Activity import
  • MongoDb Spring 在嵌套对象中查找

    我正在使用 Spring Data Mongodb 和这样的文档 id ObjectId 565c5ed433a140520cdedd7f attributes 565c5ed433a140520cdedd73 333563851 list
  • 从最近打开的应用程序中打开 Android 中的旧活动?

    我有 4 个活动 Launcher MainActivity SingleTask NotificationActivity ExampleActivity 当用户点击通知时 通知活动然后打开示例活动并完成通知活动 当我按下回键时主要活动打
  • 使用 JavaFX 将可执行 Jar 限制为一个窗口

    我正在通过构建 JavaFX 应用程序E fx 剪辑 and Java场景生成器 基本功能是登录窗口 登录后 将打开新窗口 然后登录窗口消失 目前还处于原型阶段 用完eclipse后 我想要的功能都有了 启动时显示登录窗口 代码如下 Ove
  • ConstraintLayout 源代码位于哪里?

    哪里可以找到android的源代码ConstraintLayout 我在支持框架存储库甚至谷歌搜索中都找不到它 它的源代码在这里 https android googlesource com platform frameworks opt
  • Android 应用程序中通过 VideoView 将正在播放的视频静音

    我想在我的 Android 应用程序中将 VideoView 正在播放的视频静音 我在 VideoView 类中找不到任何方法来执行此操作 知道如何做到这一点吗 我在 MediaPlayer 类中找到了一个方法 setVolume 但我无法
  • 如何将 .txt 文件的最后 5 行读入 java

    我有一个包含多个条目的文本文件 例如 hello there my name is JoeBloggs 我如何按降序阅读最后五个条目 即来自 JoeBloggs 那里 我目前有代码只能读取最后一行 public class TestLast
  • 获取定制旋转器项目

    我实现了自定义微调器 public class MyAdapter extends ArrayAdapter
  • Spark java:如何处理多部分/表单数据输入?

    我在用spark http sparkjava com 开发网络应用程序 当我想上传文件时出现问题 public final class SparkTesting public static void main final String a
  • 如何在 iText 7 中创建页面大小不等的文档

    如何在 iText 7 中创建页面大小不等的文档 iText7 可以吗 在iText5中 我使用document setPageSize and document newPage 如果您通过高级 API 添加内容 Document add
  • Maven编译错误:包不存在

    我正在尝试向现有企业项目添加 Maven 支持 这是一个多模块项目 前 2 个模块编译和打包没有问题 但我面临编译错误 我尝试在多个模块中使用相同的依赖项 我的结构是 gt parent gt pom xml gt module 1 gt
  • 我的代码线程不安全吗?

    我编写了代码来理解 CyclicBarrier 我的应用程序模拟选举 每轮选出得票少的候选人 该候选人从竞争中淘汰以获得胜利 source class ElectoralCommission public volatile boolean
  • 将Json字符串映射到java中的map或hashmap字段

    假设我从服务器返回了以下 JSON 字符串 response imageInstances one id 1 url ONE two id 2 url TWO 杰克逊代码大厦 JsonProperty 我怎样才能得到HashMap对象出来了
  • INSTALL_FAILED_NO_MATCHING_ABIS:无法提取本机库,res = -113设备

    当我在 android 8 0 设备中执行 android 项目时 我收到错误 INSTALL FAILED NO MATCHING ABIS 无法提取本机库 res 113 错误图像 https i stack imgur com 3kb
  • Java GridBagConstraints gridx 和 gridy 不工作?

    我正在尝试使用gridx and gridy定位我的按钮的约束 但它们不起作用 如果我改变gridx and gridy变量 什么也没有发生 如果我将填充更改为GridBagConstraints to NONE 仍然不行 我在这里错过了什

随机推荐

  • 需要用户使用 NTLM 重新进行身份验证

    我是 NTLM web config 中的authenication windows 有一个 asp net mvc 2 0 站点 现在 一旦用户登录 他们就会一次保持登录状态数周 该应用程序的使用正在向共享使用登录服务帐户的计算机的用户开
  • 如何使用 CUDA/Thrust 对两个数组/向量根据其中一个数组中的值进行排序

    这是一个关于编程的概念问题 总而言之 我有两个数组 向量 我需要对一个数组 向量进行排序 并将更改传播到另一个数组 向量中 这样 如果我对 arrayOne 进行排序 则对于排序中的每个交换 arrayTwo 也会发生同样的情况 现在 我知
  • Linux内核页表更新

    在linux x86 中分页 每个进程都有它自己的页面目录 页表遍历从 CR3 指向的页目录开始 每个进程共享内核页目录内容 假设三个句子是正确的 假设某个进程进入内核 模式并更新他的内核页目录内容 地址映射 访问 权利等 问题 由于内核地
  • Python 用静态图像将 mp3 转换为 mp4

    我有x文件包含一个列表mp3我想转换的文件mp3文件至mp4文件带有static png photo 似乎这里唯一的方法是使用ffmpeg但我不知道如何实现它 我编写了脚本来接受输入mp3文件夹和一个 png photo 然后它将创建新文件
  • IllegalStateException:无法更改片段的标签,以前是 android:switcher,现在是 android:switcher

    我的活动使用TabLayout ViewPager 这里的选项卡和页面的数量是动态的 具体取决于从服务器获取的数据 崩溃是通过 Crashlytics 报告的 我无法复制它 我的活动代码 Override protected void on
  • Android Studio 1.0 在 dexDebug 或 dexRelease 上构建失败

    我最近从 Android Studio 0 9 2 升级到 1 0 包括 Gradle 插件版本 1 0 0 并且在构建项目时遇到问题 每当我构建时 我都会在 dexDebug 或 dexRelease 步骤中收到以下异常 UNEXPECT
  • 如何在 Visual Basic DLL 和 C++ DLL 之间创建隔离/免注册 COM?

    我必须在 C DLL 中使用 VB COM DLL 我弄清楚了如何从 C DLL 访问 VB COM DLL 并且它可以工作 现在我遇到了一个问题 我必须使用隔离的 COM 免注册 COM 因为我无法在必须使用它的每台 PC 上注册 DLL
  • macOS 更新后 Jenkins 用户消失

    我在 Mac 上运行 Jenkins 作为 CI 服务器 使用用户 jenkins 的典型设置 它在 macOS 10 12 上运行良好 今天我将 macOS 升级到 10 13 High Sierra 升级过程完成后 Jenkins 无法
  • AWS Textract InvalidParameterException

    我有一个 Net core 客户端应用程序 根据 AWS 文档 使用带有 S3 SNS 和 SQS 的 amazon Textract 检测和分析多页文档中的文本 https docs aws amazon com texttract la
  • Android:如何停止监听电话监听器? [复制]

    这个问题在这里已经有答案了 可能的重复 Android 为什么 PhoneCallListener 在活动完成后仍然存在 https stackoverflow com questions 11666853 android why phon
  • 如何在 Google 地图中创建自定义地图?

    我正在尝试创建一个包含我家地图的 Google 地图应用程序 卧室 浴室 厨房等 使用 GPS 我会找到我现在在家里的位置 并尝试获取到我卧室的方向 步行距离 您可以使用Google的API来获取方向 我需要知道的是 如何添加我家的自定义地
  • Eclipse :: 在“打开资源”对话框中隐藏 .svn 文件

    是否可以在 Eclipse 的 打开资源 对话框 Ctrl Shift R 中隐藏 svn 文件 当你有数百个文件时 这是非常烦人的 Cheers 请尝试以下操作 项目 gt 属性 gt 资源 gt 资源过滤器 gt 添加 选择 排除全部
  • Solr:在带有空格的字符串上使用通配符

    我的问题与这里讨论的问题基本相同 带空格的 Solr 通配符查询 https stackoverflow com questions 10023133 solr wildcard query with whitespace 但这个问题没有得
  • Mandrill 通过 REST API 作为单独的消息发送给多人

    我正在尝试使用山魈发送邮件 问题是 当我将多个收件人添加到 收件人 参数时 它会多次向 收件人 列表中的所有收件人发送同一封邮件 我期望将相同的邮件单独发送给列表中的每个人 我错过了什么吗 key app key template name
  • C# 中的抽象类和接口类有什么不同?

    C 中的抽象类和接口类有什么不同 An 接口不是类 它只是一个contract定义了public一个类的成员must实施 抽象类只是一个类 您从中可以cannot创建一个实例 通常您会使用它来定义一个基类 该基类定义了一些virtual方法
  • 将包含多个事件的 ICS 文件保存到我的日历,而不是其他日历

    当我将 Excel 电子表格转换为 CSV 文件时 然后将 CSV 文件转换为 ICS 文件 我可以打开其中包含单个事件的 ICS 文件 并接受会议邀请 然后将其添加到我的日历中 使用此应用程序进行转换 http icsconverterw
  • 线性代数如何在算法中使用?

    我的几个同行都提到 学习算法时 线性代数 非常重要 我研究了各种算法并学习了一些线性代数课程 但我没有看到其中的联系 那么线性代数如何应用在算法中呢 例如 图的连接矩阵可以带来哪些有趣的事情 三个具体例子 线性代数是现代 3D 图形的基础
  • X 请求失败错误:BadAlloc(操作资源不足)

    我注意到这个问题过去已经被问过很多次 并且在网上冲浪时我发现了很多关于它的页面 然而 似乎提出的解决方案很少起作用 就我而言 问题并不涉及我编写的程序 所以我会在这里再试一次 我最近在我的笔记本电脑上安装了 Linux Mint 14 当操
  • 如何知道一个点是否在复杂的 3D 形状内(.ply 文件)

    我正在研究一个Java女巫项目真是要了我的命 经过几天在不同论坛上的研究 寻找我真正需要的东西 我来寻求你的帮助 我的数据 ply 文件 包含由许多三角形组成的 3D 形状 一个点 3D坐标 我想知道这个点是否包含在复杂的 3D 形状内 我
  • 重构 google 的 NetworkBoundResource 类以使用 RxJava 而不是 LiveData

    谷歌的android架构组件教程here https developer android com topic libraries architecture guide html有一部分解释了如何抽象通过网络获取数据的逻辑 在其中 他们使用