MVVM with Retrofit - 如何处理 Repository 中的大量 LiveData? 存储库和视图模型Repository 和 ViewModel 之间的通信

如何解决MVVM with Retrofit - 如何处理 Repository 中的大量 LiveData? 存储库和视图模型Repository 和 ViewModel 之间的通信

我正在学习如何使用带有 Retrofit 的 MVVM 的教程

https://medium.com/@ronkan26/viewmodel-using-retrofit-mvvm-architecture-f759a0291b49

用户将 MutableLiveData 放置在 Repository 类中的位置:

public class MovieRepository {
    private static final ApiInterface myInterface;
    private final MutableLiveData<EntityMovieOutputs> listOfMovies = new MutableLiveData<>();

private static MovieRepository newsRepository;

    public static MovieRepository getInstance(){
        if (newsRepository == null){
            newsRepository = new NewsRepository();
        }
        return movieRepository;
    }

    public MovieRepository(){
        myInterface = RetrofitService.getInterface();
    }

我正在构建一个简单的应用程序,我注意到我的存储库类很快被大量 MutableLiveData 对象填满。这实际上是实现 MVVM、LiveData 和 Repository 模式的正确方法吗?

enter image description here

编辑 1:________________________________________________

我创建了一个 AdminLiveData 对象,它只包含 LiveData 和 getter。

enter image description here

但是我如何获得对 ViewModel 类中的 AdminRepo 的引用,以便在 Retrofit Network 调用完成时通知 ViewModel 中的 LiveData

private AdminService  adminService;
    
public AdminRepo(Application application) {
        BaseApplication baseApplication = (BaseApplication) application;
        RetrofitClient client = baseApplication.getRetrofitClient();
        adminService = client.getRetrofit().create(AdminService.class);  

        //AdminViewModel viewModel = (AdminViewModel) .... 
        // Not sure how to get reference to the viewmodel here so I can get the 
        // LiveData object and call postValue after the retrofit calls
}

    public void getFirstPageMembers(int offset,int limit) {
        adminService.getUsersPaginitation(offset,limit).enqueue(new Callback<List<UserInfo>>() {
            @Override
            public void onResponse(@NonNull Call<List<UserInfo>> call,@NonNull Response<List<UserInfo>> response) {
                if (response.body() != null) {
                    //firstPageLiveData.postValue(response.body());
                    //Since I create the LiveData inside the ViewModel class 
                   //instead,how do I get reference to the ViewModel's LiveData?
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<UserInfo>> call,@NonNull Throwable t) {
                //firstPageLiveData.postValue(null);
            }
        });
    }

AdminViewModel

public class AdminActivityViewModel extends AndroidViewModel {

    private AdminRepo repo;

    private AdminLiveData adminLiveData = new AdminLiveData();
    
    public AdminActivityViewModel(@NonNull Application application) {
        super(application);

        repo = new AdminRepo(application);
    }

如何从我的 AdminViewModel 类中获取对 AdminRepo 的引用?

解决方法

存储库和视图模型

android 项目中的 Repository 对象应该被认为是通往外部世界的门户。与持久性设施(网络、SQLite、共享首选项)的通信发生在这一层。因为传入的数据不应该符合 android 环境。例如,传入 DTO 中的字符串日期字段应使用本地日期时间转换为日期对象,您可能需要将来自服务器的数据保存到本地数据库。此外,该层还可以执行其他与数据相关的任务,例如在内存中缓存。

ViewModel 表示用户界面中显示的数据。该层中的数据应该准备好在屏幕上呈现。例如,一个视图可能需要来自两个不同 HTTP 请求的数据,您应该在该层合并传入数据。 (不过,你可以进一步分离职责。如果这两个请求是单个任务或目的的一部分,你可以在用例层做这个操作。)从android的角度来看,视图模型有更多的责任,比如防止数据被破坏在配置更改中。在视图模型中,建议通过 LiveData 将数据呈现给视图层。这是因为 LiveData 保持数据的最后状态,并且知道视图层的生命周期(如果使用得当)。

Repository 和 ViewModel 之间的通信

首先,存储库层不能知道任何视图模型的存在,因此您不应该在存储库层中保留视图模型的引用。有一些原因,

  • 大多数情况下,用于一组交互的单个存储库对象足以满足整个应用程序的需求。如果保留对视图模型的引用,则会导致内存泄漏。
  • 如果在存储库层中使用的服务器端 API、本地存储和其他组件设计良好,与视图模型相比,存储库层不太容易发生变化。
  • 存储库层的职责是从某处获取数据,因此这意味着它与与视图相关的层无关。

当然,我们需要某种对视图模型的引用以在请求完成时通知它,但我们应该以系统的方式隐式地执行此操作,而不是直接引用。

回调:这是一种将数据发送回视图模型的旧时尚方式。在您的代码库中广泛使用回调会导致不想要的回调地狱。此外,结构化的取消机制很难使用回调实现。

LiveData:乍一看,它似乎非常适合此目的,但事实并非如此。原因可以列为

  • LiveData 被设计为具有生命周期感知能力的数据持有者。这是您的目的的开销,因为您与此层中的视图生命周期没有任何关系。
  • 在大多数情况下,数据获取操作是一次性的(就像您的情况一样),但 LiveData 是为流设计的。
  • 它没有对结构化取消的内置支持。
  • 从架构的角度来看,不应将实时数据等与 android 相关的类导入存储库层。存储库类应实现为简单的 Kotlin 或 java 类。

在您的情况下,这是特别糟糕的做法,因为 HTTP 请求不应更改存储库对象的状态。您使用 LiveData 作为一种缓存,但对此没有这样的要求,因此您应该避免这种情况。尽管如此,如果您需要在您的存储库中使用 LiveData,您应该将 MutableLiveData 作为参数传递给您的请求方法,以便您可以通过此 LiveData 发布响应或在您的请求方法中返回一个 LiveData。

RxJava:它是选项之一。它支持一次性请求(Single)、热流(Subject)和冷流(Observable)。它以某种方式支持结构化取消(CompositeDisposable)。它有一个稳定的 API,并且已经被普遍使用多年。它还使许多不同的网络操作更容易,例如并行请求、顺序请求、数据操作、线程切换等。

协程:这是另一种选择,在我看来,它是最好的。虽然它的 API 不是完全稳定,但我已经在很多不同的项目中使用过它,我没有看到任何问题。它支持一次性请求(挂起函数)、热流(Channels 和 Stateflow)和冷流(Flow)。它在需要复杂数据流的项目中非常有用。它是 Kotlin 中所有运算符的内置功能。它以一种非常优雅的方式支持结构化并发。它有许多不同的运算符函数,与 RxJava 相比,它更容易实现新的运算符函数。它还具有与 ViewModel 一起使用的有用扩展函数。

总而言之,存储库层是传入数据的网关,这是您操作数据以符合应用程序要求的第一个地方,如上所述。可以在这一层中完成的操作可以简单地列为映射传入数据、决定从何处获取数据(本地或远程源)和缓存。有很多选项可以将数据传回请求数据的类,但是 RxJava 和 Coroutines 比我上面解释的其他方法更好。在我看来,如果你对它们都不熟悉,那就把精力放在 Coroutines 上。

,

您正在寻找的解决方案取决于您的应用的设计方式。您可以尝试以下几种方法:

  • 保持您的应用程序模块化 - 正如@ADM 提到的将您的存储库拆分成更小的
  • 将实时数据移出存储库 - 没有必要在整个应用生命周期内将实时数据保存在存储库中(在您的情况下为单例),因为可能只有少数屏幕需要不同的数据。
  • 话虽如此 - 将实时数据保存在视图模型中 - 这是最标准的做法。您可以查看解释 Retrofit-ViewModel-LiveData 存储库模式的 this article
  • 如果您最终得到复杂的屏幕和许多实时数据对象,您仍然可以使用事件/状态/命令(随意调用)将实体映射到屏幕数据表示中,这些都很好地描述了 here。这样你就有了一个 LiveData<ScreenState>,你只需要映射你的实体。

此外,您可以使用 coroutines with retrofit 作为协程,因为现在推荐使用协程来处理后台操作,如果您想尝试一下,还可以使用 Kotlin 支持。

此外,在探索不同的架构或解决方案来处理您的问题 architecture-components-samplesarchitecture-samples(尽管主要使用 kotlin)时,这些链接也可能会阻碍您。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-