简体   繁体   English

MVVM 与 Retrofit - 如何处理存储库中的大量 LiveData?

[英]MVVM with Retrofit - How to deal with a lot of LiveData in Repository?

I'm following this tutorial on using MVVM with Retrofit我正在按照本教程使用 MVVM 和 Retrofit

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

where the user places MutableLiveData inside the Repository class:用户将 MutableLiveData 放置在存储库 class 中的位置:

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();
    }

I'm building a simple app and what I noticed is my repository class is quickly being filled with a lot of MutableLiveData objects.我正在构建一个简单的应用程序,我注意到我的存储库 class 很快就被很多 MutableLiveData 对象填充了。 Is this actually the correct way to implement MVVM, LiveData, and the Repository pattern?这实际上是实现 MVVM、LiveData 和存储库模式的正确方法吗?

在此处输入图像描述

Edit1:________________________________________________编辑 1:________________________________________________

I've created an AdminLiveData object that just holds the LiveData and has getters.我创建了一个AdminLiveData object,它只保存 LiveData 并具有吸气剂。

在此处输入图像描述

But how would I get reference to the ViewModel inside my AdminRepo class so I can notify the LiveData inside the ViewModel when the Retrofit Network call is complete?但是,如何在我的AdminRepo LiveData中获取对ViewModel的引用,以便在 Retrofit 网络调用完成时通知 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);
            }
        });
    }

The AdminViewModel : 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);
    }

How do I get reference to the AdminViewModel from inside my AdminRepo class?如何从我的AdminRepo class 中获取对AdminViewModel的引用?

Repository and ViewModel存储库和 ViewModel

Repository objects in android projects should be thought of as gateways to the outer world. android 项目中的存储库对象应被视为通往外部世界的网关。 Communication with persistence facilities(Network, SQLite, Shared Pref) takes place in this layer.与持久性设施(网络、SQLite、共享首选项)的通信发生在这一层。 Because of that incoming data aren't supposed to conform to the android environment.因为传入的数据不应该符合 android 环境。 For example, a string date field in incoming DTO should be converted to a date object using local date-time, you might need to save data coming from server to local DB.例如,传入 DTO 中的字符串日期字段应使用本地日期时间转换为日期 object,您可能需要将来自服务器的数据保存到本地 DB。 Additionally, other data-related tasks can be performed in this layer like caching in memory.此外,可以在这一层执行其他与数据相关的任务,例如 memory 中的缓存。

ViewModels represent the data that is shown in the user interface. ViewModel 表示用户界面中显示的数据。 Data in this layer should be ready to be presented on the screen.该层中的数据应该准备好在屏幕上呈现。 For example, a view might require data coming from two different HTTP requests, you should merge incoming data in this layer.例如,一个视图可能需要来自两个不同的 HTTP 请求的数据,您应该在这一层合并传入的数据。 (Still, you can separate responsibilities further. If these two requests are part of a single task or purpose, you can do this operation in the use case layer. ) From the android perspective, view models have more responsibility like preventing data from being destroyed in configuration changes. (不过,您可以进一步分离职责。如果这两个请求是单个任务或目的的一部分,您可以在用例层执行此操作。)从 android 的角度来看,视图模型具有更多的责任,例如防止数据被破坏在配置更改中。 In view models, it is recommended that data should be presented to the view layer by a LiveData.在视图模型中,建议数据应通过 LiveData 呈现给视图层。 It is due to fact that LiveData keeps the last state of the data and it is aware of the view layer's lifecycle (If it is used properly).这是因为 LiveData 保留了数据的最后一个 state 并且它知道视图层的生命周期(如果使用得当)。

Communication between Repository and ViewModel Repository 和 ViewModel 之间的通信

First of all, the repository layer mustn't be aware of the existence of any view model so you should not keep a reference of a view model in the repository layer.首先,存储库层必须不知道任何视图 model 的存在,因此您不应在存储库层保留视图 model 的引用。 There are some reasons for it,有一些原因,

  • Most of the time, a single repository object for a set of interactions is sufficient for the whole application.大多数情况下,用于一组交互的单个存储库 object 足以满足整个应用程序的需求。 If a reference for a view model is kept, it causes memory leaks.如果保留视图 model 的引用,则会导致 memory 泄漏。
  • If server-side API, local storage and other components that are used in the repo layer are well designed, the repository layer is less prone to changes compared to the view model.如果服务端API,repo层使用的本地存储等组件设计得很好,repository层相比视图model更不容易发生变化。
  • The responsibility of the repository layer is fetching data from somewhere so it means that it has nothing to do with the view-related layers.存储库层的职责是从某个地方获取数据,因此这意味着它与视图相关的层无关。

Of course, we need some kind of reference to the view model to notify it when a request is completed but we should do this implicitly in a systematic way rather than a direct reference.当然,我们需要某种对视图 model 的引用,以便在请求完成时通知它,但我们应该以系统的方式隐式执行此操作,而不是直接引用。

Callback: It is an old fashion way of sending data back to the view model.回调:这是一种将数据发送回视图 model 的老式方式。 Using callbacks extensively in your codebase causes callback hell that is not wanted.在代码库中广泛使用回调会导致不需要的回调地狱。 Additionally, the structured canceling mechanism is hard to implement using callbacks.此外,结构化取消机制很难使用回调实现。

LiveData: At first glance, it seems like a great fit for this purpose but it isn't. LiveData:乍一看,它似乎非常适合此目的,但事实并非如此。 The reasons can be listed as原因可以列举如下

  • LiveData is designed as a lifecycle-aware data holder. LiveData 被设计为一个生命周期感知的数据持有者。 It is overhead for your purpose because you don't have anything to do with the view's lifecycles in this layer.这对您的目的来说是开销,因为您与该层中视图的生命周期没有任何关系。
  • In most cases, data fetching operations are one-shot (like in your case) but LiveData is designed for streams.在大多数情况下,数据获取操作是一次性的(就像您的情况一样),但 LiveData 是为流设计的。
  • It doesn't have built-in support for structured canceling.它没有对结构化取消的内置支持。
  • From an architectural perspective, android related classes like live data should not be imported into the repository layer.从架构的角度来看,android 相关类如实时数据不应该导入到存储库层。 Repository classes should be implemented as a simple Kotlin or java class.存储库类应实现为简单的 Kotlin 或 java class。

In your case, it is especially bad practice because HTTP requests should not change the state of your repository object.在您的情况下,这是特别糟糕的做法,因为 HTTP 请求不应更改存储库 object 的 state。 You use LiveData as a kind of cache but there is no such requirement for this so you should avoid this.您将 LiveData 用作一种缓存,但对此没有这样的要求,因此您应该避免这种情况。 Still, if you need to use LiveData in your repo, you should either pass a MutableLiveData to your request method as a parameter so you can post the response via this LiveData or return a LiveData in your request method.尽管如此,如果您需要在您的存储库中使用 LiveData,您应该将 MutableLiveData 作为参数传递给您的请求方法,以便您可以通过此 LiveData 发布响应或在您的请求方法中返回 LiveData。

RxJava: It is one of the options. RxJava:它是选项之一。 It supports one-shot requests(Single), hot streams(Subject), and cold streams(Observable).它支持一次性请求(Single)、热流(Subject)和冷流(Observable)。 It supports structured canceling in some way(CompositeDisposable).它以某种方式支持结构化取消(CompositeDisposable)。 It has a stable API and has been used commonly for years.它有一个稳定的 API 并且已经普遍使用了多年。 It also makes many different network operations easier such as parallel requests, sequential requests, data manipulation, thread switching, and more with its operators.它还通过其运算符使许多不同的网络操作变得更容易,例如并行请求、顺序请求、数据操作、线程切换等。

Coroutines: It is another option and, in my opinion, the best.协程:这是另一种选择,在我看来,它是最好的。 Although its API is not completely stable, I have used it in many different projects and I haven't seen any problem.虽然它的 API 并不完全稳定,但我在很多不同的项目中使用过它,我没有发现任何问题。 It supports one-shot requests (suspend functions), hot streams (Channels and Stateflow), and cold streams(Flow).它支持一次性请求(挂起函数)、热流(Channels 和 Stateflow)和冷流(Flow)。 It is really useful in projects requiring complex data flows.它在需要复杂数据流的项目中非常有用。 It is a built-in feature in Kotlin with all operators.它是 Kotlin 中所有运算符的内置功能。 It supports structured concurrency in a really elegant way.它以一种非常优雅的方式支持结构化并发。 It has many different operator functions and it is easier to implement new operator functions compare to RxJava.它有许多不同的运算符函数,与 RxJava 相比,它更容易实现新的运算符函数。 It also has useful extension functions to use with ViewModel.它还具有与 ViewModel 一起使用的有用扩展功能。

To sum up, the repository layer is a gateway for incoming data, this is the first place where you manipulate data to conform requirements of your application as I have mentioned above.总而言之,存储库层是传入数据的网关,这是您操作数据以符合我上面提到的应用程序要求的第一个地方。 The operations that could be done in this layer can be listed shortly as mapping incoming data, deciding where to fetch data (local or remote source), and caching.可以在这一层中完成的操作可以简单地列出为映射传入数据、决定从哪里获取数据(本地或远程源)和缓存。 There are many options to pass data back to class requesting the data but RxJava and Coroutines are better than others as I have explained above.有许多选项可以将数据传回 class 请求数据,但 RxJava 和 Coroutines 比其他更好,正如我在上面解释的那样。 In my opinion, if you are not familiar with both of them, put your effort into Coroutines.在我看来,如果你对它们都不熟悉,那就把你的精力放在 Coroutines 上。

The solution you're looking for depends on how your app is designed.您正在寻找的解决方案取决于您的应用程序的设计方式。 There are several things you can try out:您可以尝试以下几件事:

  • Keep your app modularized - as @ADM mentioned split your repository into smaller保持您的应用程序模块化 - 正如@ADM 提到的将您的存储库拆分为更小的
  • Move live data out of repository - it is unnecessary to keep live data in a repository (in your case singleton) for the entire app lifecycle while there might be only few screens that need different data.将实时数据移出存储库 - 在整个应用程序生命周期中,没有必要将实时数据保留在存储库中(在您的情况下为单例),而可能只有少数屏幕需要不同的数据。
  • That's being said - keep your live data in view models - this is the most standard way of doing.话虽如此 - 将您的实时数据保存在视图模型中 - 这是最标准的做法。 You can take a look at this article that explains Retrofit-ViewModel-LiveData repository pattern您可以查看这篇解释 Retrofit-ViewModel-LiveData 存储库模式的文章
  • If you end up with complicated screen and many live data objects you can still map entities into screen data representation with events / states /commands (call it as you want) which are pretty well described here .如果您最终得到复杂的屏幕和许多实时数据对象,您仍然可以将 map 实体转换为带有事件/状态/命令(随意调用)的屏幕数据表示,这些在此处进行了很好的描述。 This way you have single LiveData<ScreenState> and you just have to map your entities.这样你就有了一个LiveData<ScreenState>并且你只需要 map 你的实体。

Additionaly you could use coroutines with retrofit as coroutines are recomended way now for handling background operations and have Kotlin support if you wanted to give it a try.另外,您可以将协程与 retrofit 一起使用,因为协程现在被推荐用于处理后台操作,并且如果您想尝试一下,则支持 Kotlin。

Also these links might halpe you when exploring different architectures or solutions for handling your problem architecture-components-samples or architecture-samples (mostly using kotlin though).此外,在探索不同的架构或解决方案来处理您的问题架构组件示例架构示例时,这些链接可能会阻碍您(尽管主要使用 kotlin)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM