带有 Retrofit 的 MVVM - 如何处理存储库中的大量 LiveData?
MVVM with Retrofit - How to deal with a lot of LiveData in Repository?
我正在学习有关将 MVVM 与 Retrofit 结合使用的教程
https://medium.com/@ronkan26/viewmodel-using-retrofit-mvvm-architecture-f759a0291b49
用户将 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();
}
我正在构建一个简单的应用程序,我注意到我的存储库 class 很快被大量 MutableLiveData 对象填满。这实际上是实现 MVVM、LiveData 和 Repository 模式的正确方法吗?
编辑 1:________________________________________________
我创建了一个 AdminLiveData
对象,它只包含 LiveData 并具有吸气剂。
但是我如何在 AdminRepo
class 中获取对 ViewModel
的引用,以便在 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);
}
如何从我的 AdminRepo
class 中获取对 AdminViewModel
的引用?
您正在寻找的解决方案取决于您的应用程序的设计方式。您可以尝试几种方法:
- 保持您的应用程序模块化 - 正如@ADM 提到的将您的存储库拆分成更小的
- 将实时数据移出存储库 - 没有必要在整个应用程序生命周期中将实时数据保存在存储库中(在您的情况下为单例),因为可能只有几个屏幕需要不同的数据。
- 话虽如此 - 将您的实时数据保存在视图模型中 - 这是最标准的做法。您可以查看 this article,其中解释了 Retrofit-ViewModel-LiveData 存储库模式
- 如果您最终得到一个复杂的屏幕和许多实时数据对象,您仍然可以使用事件/状态/命令(随意调用)将实体映射到屏幕数据表示中,这些描述得很好 here .这样你就有了一个
LiveData<ScreenState>
,你只需要映射你的实体。
此外,您可以使用 coroutines with retrofit,因为现在推荐使用协程来处理后台操作,如果您想尝试一下,还可以使用 Kotlin 支持。
此外,这些链接可能会帮助您探索不同的架构或解决方案来处理您的问题architecture-components-samples or architecture-samples(虽然主要使用 kotlin)。
存储库和 ViewModel
android 项目中的存储库对象应被视为通往外部世界的门户。与持久性设施(Network、SQLite、Shared Pref)的通信发生在这一层。因为传入的数据不应该符合 android 环境。例如,传入 DTO 中的字符串日期字段应使用本地日期时间转换为日期对象,您可能需要将来自服务器的数据保存到本地数据库。此外,其他与数据相关的任务可以在这一层执行,例如在内存中缓存。
ViewModel 表示用户界面中显示的数据。该层中的数据应准备好显示在屏幕上。例如,一个视图可能需要来自两个不同 HTTP 请求的数据,您应该合并该层中的传入数据。 (不过,您可以进一步分离职责。如果这两个请求是单个任务或目的的一部分,您可以在用例层执行此操作。)从 android 的角度来看,视图模型有更多的职责,例如防止数据在配置更改中被破坏。在视图模型中,建议通过 LiveData 将数据呈现给视图层。这是因为 LiveData 保留了数据的最后状态,并且它知道视图层的生命周期(如果使用得当)。
Repository 和 ViewModel 之间的通信
首先,存储库层不能知道任何视图模型的存在,因此您不应该在存储库层中保留视图模型的引用。有一些原因,
- 大多数时候,用于一组交互的单个存储库对象对于整个应用程序来说就足够了。如果保留视图模型的引用,则会导致内存泄漏。
- 如果服务器端API、本地存储和 repo 层中使用的其他组件设计得很好,与视图模型相比,存储库层更不容易发生变化。
- 存储库层的职责是从某处获取数据,因此这意味着它与视图相关层无关。
当然,我们需要某种对视图模型的引用,以便在请求完成时通知它,但我们应该以系统的方式隐式执行此操作,而不是直接引用。
回调: 这是一种将数据发送回视图模型的老式方式。在您的代码库中广泛使用回调会导致不希望出现的回调地狱。此外,结构化取消机制很难使用回调实现。
LiveData: 乍一看,它似乎很适合这个目的,但事实并非如此。原因可以列为
- LiveData 被设计为生命周期感知型数据持有者。这对您的目的来说是开销,因为您与该层中的视图生命周期没有任何关系。
- 在大多数情况下,数据获取操作是一次性的(就像您的情况一样),但 LiveData 是为流设计的。
- 它没有对结构化取消的内置支持。
- 从体系结构的角度来看,android 相关的 class 数据(如实时数据)不应导入存储库层。存储库 classes 应作为简单的 Kotlin 或 java class.
实现
在您的情况下,这是特别糟糕的做法,因为 HTTP 请求不应更改存储库对象的状态。您将 LiveData 用作一种缓存,但对此没有这样的要求,因此您应该避免这种情况。尽管如此,如果您需要在您的 repo 中使用 LiveData,您应该将 MutableLiveData 作为参数传递给您的请求方法,以便您可以通过此 LiveData post 响应或在您的请求方法中 return 一个 LiveData .
RxJava: 是选项之一。它支持一次性请求(Single)、热流(Subject)和冷流(Observable)。它以某种方式支持结构化取消(CompositeDisposable)。它具有稳定的 API 并且已经普遍使用多年。它还使许多不同的网络操作更加容易,例如并行请求、顺序请求、数据操作、线程切换等等。
协程: 这是另一种选择,在我看来,这是最好的。虽然它的 API 不是完全稳定的,但我已经在很多不同的项目中使用过它,没有发现任何问题。它支持一次性请求(挂起函数)、热流(Channels 和 Stateflow)和冷流(Flow)。它在需要复杂数据流的项目中非常有用。它是 Kotlin 中所有运算符的内置功能。它以一种非常优雅的方式支持结构化并发。它有许多不同的运算符函数,与 RxJava 相比,它更容易实现新的运算符函数。它还具有与 ViewModel 一起使用的有用扩展功能。
总而言之,存储库层是传入数据的网关,这是您操作数据以符合我上面提到的应用程序要求的第一个地方。可以在这一层完成的操作可以简单地列为映射传入数据、决定从何处获取数据(本地或远程源)以及缓存。有很多选项可以将数据传回 class 请求数据,但正如我在上面解释的那样,RxJava 和 Coroutines 比其他的要好。在我看来,如果你对这两个都不熟悉,那就把精力放在Coroutines上。
我正在学习有关将 MVVM 与 Retrofit 结合使用的教程
https://medium.com/@ronkan26/viewmodel-using-retrofit-mvvm-architecture-f759a0291b49
用户将 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();
}
我正在构建一个简单的应用程序,我注意到我的存储库 class 很快被大量 MutableLiveData 对象填满。这实际上是实现 MVVM、LiveData 和 Repository 模式的正确方法吗?
编辑 1:________________________________________________
我创建了一个 AdminLiveData
对象,它只包含 LiveData 并具有吸气剂。
但是我如何在 AdminRepo
class 中获取对 ViewModel
的引用,以便在 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);
}
如何从我的 AdminRepo
class 中获取对 AdminViewModel
的引用?
您正在寻找的解决方案取决于您的应用程序的设计方式。您可以尝试几种方法:
- 保持您的应用程序模块化 - 正如@ADM 提到的将您的存储库拆分成更小的
- 将实时数据移出存储库 - 没有必要在整个应用程序生命周期中将实时数据保存在存储库中(在您的情况下为单例),因为可能只有几个屏幕需要不同的数据。
- 话虽如此 - 将您的实时数据保存在视图模型中 - 这是最标准的做法。您可以查看 this article,其中解释了 Retrofit-ViewModel-LiveData 存储库模式
- 如果您最终得到一个复杂的屏幕和许多实时数据对象,您仍然可以使用事件/状态/命令(随意调用)将实体映射到屏幕数据表示中,这些描述得很好 here .这样你就有了一个
LiveData<ScreenState>
,你只需要映射你的实体。
此外,您可以使用 coroutines with retrofit,因为现在推荐使用协程来处理后台操作,如果您想尝试一下,还可以使用 Kotlin 支持。
此外,这些链接可能会帮助您探索不同的架构或解决方案来处理您的问题architecture-components-samples or architecture-samples(虽然主要使用 kotlin)。
存储库和 ViewModel
android 项目中的存储库对象应被视为通往外部世界的门户。与持久性设施(Network、SQLite、Shared Pref)的通信发生在这一层。因为传入的数据不应该符合 android 环境。例如,传入 DTO 中的字符串日期字段应使用本地日期时间转换为日期对象,您可能需要将来自服务器的数据保存到本地数据库。此外,其他与数据相关的任务可以在这一层执行,例如在内存中缓存。
ViewModel 表示用户界面中显示的数据。该层中的数据应准备好显示在屏幕上。例如,一个视图可能需要来自两个不同 HTTP 请求的数据,您应该合并该层中的传入数据。 (不过,您可以进一步分离职责。如果这两个请求是单个任务或目的的一部分,您可以在用例层执行此操作。)从 android 的角度来看,视图模型有更多的职责,例如防止数据在配置更改中被破坏。在视图模型中,建议通过 LiveData 将数据呈现给视图层。这是因为 LiveData 保留了数据的最后状态,并且它知道视图层的生命周期(如果使用得当)。
Repository 和 ViewModel 之间的通信
首先,存储库层不能知道任何视图模型的存在,因此您不应该在存储库层中保留视图模型的引用。有一些原因,
- 大多数时候,用于一组交互的单个存储库对象对于整个应用程序来说就足够了。如果保留视图模型的引用,则会导致内存泄漏。
- 如果服务器端API、本地存储和 repo 层中使用的其他组件设计得很好,与视图模型相比,存储库层更不容易发生变化。
- 存储库层的职责是从某处获取数据,因此这意味着它与视图相关层无关。
当然,我们需要某种对视图模型的引用,以便在请求完成时通知它,但我们应该以系统的方式隐式执行此操作,而不是直接引用。
回调: 这是一种将数据发送回视图模型的老式方式。在您的代码库中广泛使用回调会导致不希望出现的回调地狱。此外,结构化取消机制很难使用回调实现。
LiveData: 乍一看,它似乎很适合这个目的,但事实并非如此。原因可以列为
- LiveData 被设计为生命周期感知型数据持有者。这对您的目的来说是开销,因为您与该层中的视图生命周期没有任何关系。
- 在大多数情况下,数据获取操作是一次性的(就像您的情况一样),但 LiveData 是为流设计的。
- 它没有对结构化取消的内置支持。
- 从体系结构的角度来看,android 相关的 class 数据(如实时数据)不应导入存储库层。存储库 classes 应作为简单的 Kotlin 或 java class. 实现
在您的情况下,这是特别糟糕的做法,因为 HTTP 请求不应更改存储库对象的状态。您将 LiveData 用作一种缓存,但对此没有这样的要求,因此您应该避免这种情况。尽管如此,如果您需要在您的 repo 中使用 LiveData,您应该将 MutableLiveData 作为参数传递给您的请求方法,以便您可以通过此 LiveData post 响应或在您的请求方法中 return 一个 LiveData .
RxJava: 是选项之一。它支持一次性请求(Single)、热流(Subject)和冷流(Observable)。它以某种方式支持结构化取消(CompositeDisposable)。它具有稳定的 API 并且已经普遍使用多年。它还使许多不同的网络操作更加容易,例如并行请求、顺序请求、数据操作、线程切换等等。
协程: 这是另一种选择,在我看来,这是最好的。虽然它的 API 不是完全稳定的,但我已经在很多不同的项目中使用过它,没有发现任何问题。它支持一次性请求(挂起函数)、热流(Channels 和 Stateflow)和冷流(Flow)。它在需要复杂数据流的项目中非常有用。它是 Kotlin 中所有运算符的内置功能。它以一种非常优雅的方式支持结构化并发。它有许多不同的运算符函数,与 RxJava 相比,它更容易实现新的运算符函数。它还具有与 ViewModel 一起使用的有用扩展功能。
总而言之,存储库层是传入数据的网关,这是您操作数据以符合我上面提到的应用程序要求的第一个地方。可以在这一层完成的操作可以简单地列为映射传入数据、决定从何处获取数据(本地或远程源)以及缓存。有很多选项可以将数据传回 class 请求数据,但正如我在上面解释的那样,RxJava 和 Coroutines 比其他的要好。在我看来,如果你对这两个都不熟悉,那就把精力放在Coroutines上。