具有复杂房间实时数据的嵌套 Recyclerviews
Nested Recyclerviews with Complex Room LiveData
我收集了 parent objects,每个收集了 child objects。调用这些 ParentModel
s 和 ChildModel
s.
我想在屏幕上显示呈现的 ParentModels 的 RecyclerView,每个包含呈现的 ChildModels 的 RecyclerView。
希望避免有一个上帝 LiveData 仅仅因为一个 属性 的一个 ChildModel 发生变化而重绘所有内容,我打算将它们分开。
我不知道如何使用 Recyclerview Adapters 和 Holders 加上我需要的任何 Fragments 和 ViewModels 来构建它。现在我有
class MyFragment: Fragment() {
private lateinit val mViewModel: FragmentViewModel
// ...
fun onViewCreated(/*...*/) {
val parentAdapter = ParentAdapter()
view.findViewById<RecyclerView>(/*...*/).apply {
adapter = parentAdapter
//...
}
viewModel.getParents().observe(this, Observer {
parentAdapter.setParents(it)
}
}
}
class FragmentViewModel @Inject constructor(repository: RoomRepo): ViewModel() {
mParents: LiveData<List<ParentModel>> = repository.getParents()
fun getParents() = mParents
//...
}
class ParentAdapter: RecyclerView.Adapter<ParentHolder>() {
private lateinit var mParents: List<ParentModel>
fun setParents(list: List<ParentModel>) {
mParents = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, /*...*/) {
return ParentHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent, parent, false))
}
override fun onBindViewHolder(holder: ParentHolder, position: Int) {
holder.bind(/*UNKNOWN*/)
}
// ...
inner class ParentHolder(private val mView: View): RecyclerView.ViewHolder(mView) {
fun bind(/*UNKNOWN*/) {
// WHAT TO DO HERE???
}
}
}
加上我的R.layout.parent
(我省略了其他不相关的东西,比如只画一条水平线的视图,但这就是我将 RecyclerView 嵌套在 LinearLayout 中的原因):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
我不假思索地写了一个 ChildAdapter、ChildHolder 和其他一些东西,因为我认为这很容易实现,但在这一点上我脑子里乱糟糟的,我可能看不到明显的东西。
我已经根据基础数据正确加载了第一个 RecyclerView。但是这个 parent recyclerview 还需要:
- 取children基于单个parent.id
- 为显示 children
的单个 parent recyclerview 项目创建 child recyclerview
房间 returns 一个 LiveData> 来自函数 repository.getChildrenByParentId(id: Long)。这就是我正在处理的数据。
但是我在哪里获取这个,我如何将它连接到属于 parent recyclerview 的相关 child recyclerview?
我不想有这样的神碎片
viewModel.getParents().observe(...) { parentAdapter.update(it) } 和 也 必须做某种 viewModel.getChildren()。观察(...){ parentAdapter.updateChildren(它)}
因为这会破坏关注点分离。在我看来 parent recyclerview 中的每个项目都应该有一个视图模型来获取属于它的 children,然后创建一个 recyclerview 并使用 ChildAdapter 来显示这些 children,但我似乎无法弄清楚在哪里插入 ChildFragment 和 ChildViewModel(其中包含 repository.getChildrenByParentId
)才能使这一切正常工作。
我在网上找到的所有示例似乎都没有帮助,因为它们使用没有 LiveData 的人为示例和将所有内容放入单个适配器中的神 fragment/activity。
我真的有 1 个适配器可以渲染所有内容,使用 DiffUtil
(或其异步版本)class 以确保我不(并且我引用)“重绘一切只是因为一个 ChildModel 中的一个 属性 改变了 ”。
我会将构建(和提供)数据的复杂责任转移到您的存储库(或者,如果您希望更接近它,则转移到充当 1 个或多个协调器的 ViewModel(我不知道您的模型的外观,所以我只是在想象)提供数据的存储库。
这将允许您向 ui 提供一个更加精心策划的不可变列表 ParentsAndChildren
以及您的 RecyclerView/Adapter职责突然简单多了,显示这个,并为每一行绑定正确的视图。您的 UI 突然变快了,在主线程上花费的时间少了很多,您甚至可以对创建此列表的逻辑进行单元测试,完全独立于您的 Activity/Fragment。
我想象 ParentsAndChildren 是这样的:
class ParentChildren(parent: Parent?, children: Children?)
当 parent 不为 null 而 children 为 null 时,您的绑定可以膨胀一个视图。当 children 不为 null 时,您知道它是一个 children(您也可以包括 parent,这取决于您如何构建此数据)。问题在这里解决了,你的适配器看起来像
class YourAdapter : ListAdapter<ParentChildren, RecyclerView.ViewHolder>(DiffUtilCallback()) {
...
您需要实施您的 DiffUtilCallback()
:
internal class DiffUtilCallback : DiffUtil.ItemCallback<ParentChildren>() {
及其两个方法 (areContentsTheSame
, areItemsTheSame
).
以及您的适配器的两种方法:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
viewTypeParent -> YourParentViewHolder(inflater.inflate(R.layout.your_layout_for_parent), parent, false))
viewTypeChildren -> YourChildrenViewHolder(inflater.inflate(R.layout.your_layout_for_children), parent, false))
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
我将有一个抽象基础来进一步简化适配器:
internal abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(data: ParentChildren)
}
这让您可以
// I'm writing pseudo code here... keep it in mind
internal class ParentViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.parent?.name
}
}
internal class ChildrenViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.children?.name
}
}
你懂的。
现在...ListAdapter<>
有一个名为 submitList(T)
的方法,其中 T 是上述 pseudo-example.
中适配器 ParentChildren
的类型
就我而言,现在您必须提供此 Activity 或托管此适配器的片段,通过 LiveData 或 任何您喜欢的列表 为您的体系结构。
它可以是一个存储库,将它传递给 viewModel 内的 MutableLiveData,并且 ViewModel 公开一个 LiveData<List<ParentChildren>
或类似于 UI.
天空无极限。
这改变了将这些数据放在一起的复杂性,更接近数据所在的位置,以及 SQL/Room 的力量可以利用您如何组合和处理这些数据的位置,而不管 UI 是什么需要或想要处理它。
这是我的建议,但基于我对您的项目的了解非常有限。
祝你好运! :)
我收集了 parent objects,每个收集了 child objects。调用这些 ParentModel
s 和 ChildModel
s.
我想在屏幕上显示呈现的 ParentModels 的 RecyclerView,每个包含呈现的 ChildModels 的 RecyclerView。
希望避免有一个上帝 LiveData 仅仅因为一个 属性 的一个 ChildModel 发生变化而重绘所有内容,我打算将它们分开。
我不知道如何使用 Recyclerview Adapters 和 Holders 加上我需要的任何 Fragments 和 ViewModels 来构建它。现在我有
class MyFragment: Fragment() {
private lateinit val mViewModel: FragmentViewModel
// ...
fun onViewCreated(/*...*/) {
val parentAdapter = ParentAdapter()
view.findViewById<RecyclerView>(/*...*/).apply {
adapter = parentAdapter
//...
}
viewModel.getParents().observe(this, Observer {
parentAdapter.setParents(it)
}
}
}
class FragmentViewModel @Inject constructor(repository: RoomRepo): ViewModel() {
mParents: LiveData<List<ParentModel>> = repository.getParents()
fun getParents() = mParents
//...
}
class ParentAdapter: RecyclerView.Adapter<ParentHolder>() {
private lateinit var mParents: List<ParentModel>
fun setParents(list: List<ParentModel>) {
mParents = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, /*...*/) {
return ParentHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent, parent, false))
}
override fun onBindViewHolder(holder: ParentHolder, position: Int) {
holder.bind(/*UNKNOWN*/)
}
// ...
inner class ParentHolder(private val mView: View): RecyclerView.ViewHolder(mView) {
fun bind(/*UNKNOWN*/) {
// WHAT TO DO HERE???
}
}
}
加上我的R.layout.parent
(我省略了其他不相关的东西,比如只画一条水平线的视图,但这就是我将 RecyclerView 嵌套在 LinearLayout 中的原因):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
我不假思索地写了一个 ChildAdapter、ChildHolder 和其他一些东西,因为我认为这很容易实现,但在这一点上我脑子里乱糟糟的,我可能看不到明显的东西。
我已经根据基础数据正确加载了第一个 RecyclerView。但是这个 parent recyclerview 还需要:
- 取children基于单个parent.id
- 为显示 children 的单个 parent recyclerview 项目创建 child recyclerview
房间 returns 一个 LiveData> 来自函数 repository.getChildrenByParentId(id: Long)。这就是我正在处理的数据。
但是我在哪里获取这个,我如何将它连接到属于 parent recyclerview 的相关 child recyclerview?
我不想有这样的神碎片 viewModel.getParents().observe(...) { parentAdapter.update(it) } 和 也 必须做某种 viewModel.getChildren()。观察(...){ parentAdapter.updateChildren(它)}
因为这会破坏关注点分离。在我看来 parent recyclerview 中的每个项目都应该有一个视图模型来获取属于它的 children,然后创建一个 recyclerview 并使用 ChildAdapter 来显示这些 children,但我似乎无法弄清楚在哪里插入 ChildFragment 和 ChildViewModel(其中包含 repository.getChildrenByParentId
)才能使这一切正常工作。
我在网上找到的所有示例似乎都没有帮助,因为它们使用没有 LiveData 的人为示例和将所有内容放入单个适配器中的神 fragment/activity。
我真的有 1 个适配器可以渲染所有内容,使用 DiffUtil
(或其异步版本)class 以确保我不(并且我引用)“重绘一切只是因为一个 ChildModel 中的一个 属性 改变了 ”。
我会将构建(和提供)数据的复杂责任转移到您的存储库(或者,如果您希望更接近它,则转移到充当 1 个或多个协调器的 ViewModel(我不知道您的模型的外观,所以我只是在想象)提供数据的存储库。
这将允许您向 ui 提供一个更加精心策划的不可变列表 ParentsAndChildren
以及您的 RecyclerView/Adapter职责突然简单多了,显示这个,并为每一行绑定正确的视图。您的 UI 突然变快了,在主线程上花费的时间少了很多,您甚至可以对创建此列表的逻辑进行单元测试,完全独立于您的 Activity/Fragment。
我想象 ParentsAndChildren 是这样的:
class ParentChildren(parent: Parent?, children: Children?)
当 parent 不为 null 而 children 为 null 时,您的绑定可以膨胀一个视图。当 children 不为 null 时,您知道它是一个 children(您也可以包括 parent,这取决于您如何构建此数据)。问题在这里解决了,你的适配器看起来像
class YourAdapter : ListAdapter<ParentChildren, RecyclerView.ViewHolder>(DiffUtilCallback()) {
...
您需要实施您的 DiffUtilCallback()
:
internal class DiffUtilCallback : DiffUtil.ItemCallback<ParentChildren>() {
及其两个方法 (areContentsTheSame
, areItemsTheSame
).
以及您的适配器的两种方法:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
viewTypeParent -> YourParentViewHolder(inflater.inflate(R.layout.your_layout_for_parent), parent, false))
viewTypeChildren -> YourChildrenViewHolder(inflater.inflate(R.layout.your_layout_for_children), parent, false))
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
我将有一个抽象基础来进一步简化适配器:
internal abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(data: ParentChildren)
}
这让您可以
// I'm writing pseudo code here... keep it in mind
internal class ParentViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.parent?.name
}
}
internal class ChildrenViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.children?.name
}
}
你懂的。
现在...ListAdapter<>
有一个名为 submitList(T)
的方法,其中 T 是上述 pseudo-example.
ParentChildren
的类型
就我而言,现在您必须提供此 Activity 或托管此适配器的片段,通过 LiveData 或 任何您喜欢的列表 为您的体系结构。
它可以是一个存储库,将它传递给 viewModel 内的 MutableLiveData,并且 ViewModel 公开一个 LiveData<List<ParentChildren>
或类似于 UI.
天空无极限。
这改变了将这些数据放在一起的复杂性,更接近数据所在的位置,以及 SQL/Room 的力量可以利用您如何组合和处理这些数据的位置,而不管 UI 是什么需要或想要处理它。
这是我的建议,但基于我对您的项目的了解非常有限。
祝你好运! :)