使用 UI 特定数据增强 PagedList 且不破坏 MVVM 模式的最佳方法
Best way to enhance PagedList with UI-specific data and not break MVVM pattern
我是 MVVM 模式的新手,但我真的很喜欢 PagedList 如何简化存储库中分页数据的处理。
但是现在我遇到了以下情况:
我有这样的存储库:
fun getLibraryItems(): LiveData<PagedList<ItemInfo>>
其中 ItemInfo
是特定于存储库的,'know' 与 UI 无关。
- 接下来我想 'enhance' 这个数据对象与 UI 特定数据(映射到另一个 ui 数据对象),我只能从上下文感知组件中获取,f.e。它是 Drawable 资源。
但我无法直接从 PagedList<X>
直接 'map' 到另一个 PagedList<Y>
,如果我想要的话 - 我需要更新我的数据源以接受 'mapper' 函数,像这样:
fun <T> getLibraryItems(mapper: (ItemInfo) -> T): LiveData<PagedList<T>> {
return dataSource
.getLibraryItems()
.map(mapper)
.toLiveData()
}
这样我可以 'map' ItemInfo
到 UI-特定类型 T
,但我不能在 ViewModel 中这样做,因为在 ViewModel 中加载 Drawable 资源是 (据我了解)反模式。
我不明白我应该如何以及在何处称呼这个 'mapper',应该是 Activity/Fragment 还是我遗漏了某些东西和过于复杂的东西。
我认为您可以按照您的建议将数据映射到 ViewModel 中,但是您没有直接在此处加载 Drawable 资源,而是仅传递了它的标识符。然后在您的 UI 层(RecyclerView Adapter,Activity 或其他)通过其标识符加载资源。这样您就不必在 ViewModel 中处理特定于上下文的方法。此外,如果您不想处理 android 资源 ID(例如 R.drawable.my_image),您必须保留自己的 ID 系统,然后将其映射到内部的 android 资源 ID UI层,虽然我个人认为这是多余的并发症。
编辑:重新阅读您的问题后,我发现我的 post 没有完全回答它,所以这里有更多关于架构的信息:
由于您想将域对象映射到 UI 特定对象,所以在 ViewModel 中这样做不是最佳做法,也不是 clean architecture 方式,这是对的。因此,正确的方法是在显示对象之前在 UI 层中进行此类映射。由于您使用 PagedList,我可以假设您的 RecyclerView 适配器扩展了 PagedListAdapter。然后你可以创建一个抽象来支持适配器内部的映射。
abstract class MyPagedAdapter<T, R, VH: RecyclerView.ViewHolder>(
val dataMapper: (T)->R,
diffCallback: DiffUtil.ItemCallback<T>
): PagedListAdapter<T, VH>(diffCallback) {
fun getMappedItem(position: Int) = dataMapper.invoke(getItem(position))
}
class LibraryItemAdapter(
dataMapper: (LibraryItem)->UILibraryItem,
): MyPagedAdapter<LibraryItem, UILibraryItem, ViewHolderType>(dataMapper, /* provide diff callback here */) {
/* implement onCreateViewHolder and getItemViewType here */
override fun onBindViewHolder(holder: ViewHolderType, position: Int) {
val item = getMappedItem(position)
// configure view holder to display selected item
}
}
// Then inside your fragment/activity:
val mapper: (LibraryItem) -> UILibraryItem = { /* use your context-aware components to create an object for UI */ }
recyclerView.adapter = LibraryItemAdapter(mapper)
P.S:我还想强调一下,将 ViewModel/Repository/elsewhere 中的整个列表映射到另一个包含 Drawable、Bitmap 或任何其他大型结构的 class 是一个坏主意,你最终可能会用 Drawables 淹没内存,而这些 Drawables 目前甚至可能不会显示。仅在您要使用它们时获取可绘制对象,不要将它们存储在列表中。我最初的回答和后来的编辑都让你无法这样做
P.P.S.: 不要在映射器中投入太多工作,因为当用户快速滚动大型列表时,它可能会大量加载 UI 线程。在 domain/repository 层执行所有业务逻辑。
我是 MVVM 模式的新手,但我真的很喜欢 PagedList 如何简化存储库中分页数据的处理。
但是现在我遇到了以下情况:
我有这样的存储库:
fun getLibraryItems(): LiveData<PagedList<ItemInfo>>
其中 ItemInfo
是特定于存储库的,'know' 与 UI 无关。
- 接下来我想 'enhance' 这个数据对象与 UI 特定数据(映射到另一个 ui 数据对象),我只能从上下文感知组件中获取,f.e。它是 Drawable 资源。
但我无法直接从 PagedList<X>
直接 'map' 到另一个 PagedList<Y>
,如果我想要的话 - 我需要更新我的数据源以接受 'mapper' 函数,像这样:
fun <T> getLibraryItems(mapper: (ItemInfo) -> T): LiveData<PagedList<T>> {
return dataSource
.getLibraryItems()
.map(mapper)
.toLiveData()
}
这样我可以 'map' ItemInfo
到 UI-特定类型 T
,但我不能在 ViewModel 中这样做,因为在 ViewModel 中加载 Drawable 资源是 (据我了解)反模式。
我不明白我应该如何以及在何处称呼这个 'mapper',应该是 Activity/Fragment 还是我遗漏了某些东西和过于复杂的东西。
我认为您可以按照您的建议将数据映射到 ViewModel 中,但是您没有直接在此处加载 Drawable 资源,而是仅传递了它的标识符。然后在您的 UI 层(RecyclerView Adapter,Activity 或其他)通过其标识符加载资源。这样您就不必在 ViewModel 中处理特定于上下文的方法。此外,如果您不想处理 android 资源 ID(例如 R.drawable.my_image),您必须保留自己的 ID 系统,然后将其映射到内部的 android 资源 ID UI层,虽然我个人认为这是多余的并发症。
编辑:重新阅读您的问题后,我发现我的 post 没有完全回答它,所以这里有更多关于架构的信息:
由于您想将域对象映射到 UI 特定对象,所以在 ViewModel 中这样做不是最佳做法,也不是 clean architecture 方式,这是对的。因此,正确的方法是在显示对象之前在 UI 层中进行此类映射。由于您使用 PagedList,我可以假设您的 RecyclerView 适配器扩展了 PagedListAdapter。然后你可以创建一个抽象来支持适配器内部的映射。
abstract class MyPagedAdapter<T, R, VH: RecyclerView.ViewHolder>(
val dataMapper: (T)->R,
diffCallback: DiffUtil.ItemCallback<T>
): PagedListAdapter<T, VH>(diffCallback) {
fun getMappedItem(position: Int) = dataMapper.invoke(getItem(position))
}
class LibraryItemAdapter(
dataMapper: (LibraryItem)->UILibraryItem,
): MyPagedAdapter<LibraryItem, UILibraryItem, ViewHolderType>(dataMapper, /* provide diff callback here */) {
/* implement onCreateViewHolder and getItemViewType here */
override fun onBindViewHolder(holder: ViewHolderType, position: Int) {
val item = getMappedItem(position)
// configure view holder to display selected item
}
}
// Then inside your fragment/activity:
val mapper: (LibraryItem) -> UILibraryItem = { /* use your context-aware components to create an object for UI */ }
recyclerView.adapter = LibraryItemAdapter(mapper)
P.S:我还想强调一下,将 ViewModel/Repository/elsewhere 中的整个列表映射到另一个包含 Drawable、Bitmap 或任何其他大型结构的 class 是一个坏主意,你最终可能会用 Drawables 淹没内存,而这些 Drawables 目前甚至可能不会显示。仅在您要使用它们时获取可绘制对象,不要将它们存储在列表中。我最初的回答和后来的编辑都让你无法这样做
P.P.S.: 不要在映射器中投入太多工作,因为当用户快速滚动大型列表时,它可能会大量加载 UI 线程。在 domain/repository 层执行所有业务逻辑。