带有复杂房间LiveData的嵌套Recyclerviews

时间:2019-07-03 18:01:28

标签: android kotlin android-recyclerview recycler-adapter android-livedata

我有一个父对象的集合,每个父对象都有一个子对象的集合。叫这些ParentModelChildModel

我想在屏幕上显示一个呈现的ParentModels的RecyclerView,每个视图都包含一个呈现的ChildModels的RecyclerView。

我希望避免因为一个ChildModel的一个属性发生更改而重绘所有内容的上帝LiveData,我打算将它们分开。

我无法弄清楚如何使用Recyclerview适配器和支架以及所需的任何片段和视图模型来构造它。现在我有

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(我省略了其他不相关的东西,例如只画一条水平线的View,但这就是为什么我将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。但是此父级recyclerview还需要:

  1. 基于一个parent.id获取孩子
  2. 为显示子项的单个父级recyclerview项目创建子级recyclerview

Room从函数repository.getChildrenByParentId(id:Long)返回一个LiveData>。这就是我正在处理的数据。

但是我应该在哪里获取它,如何将其挂钩到属于父recyclerview的相关子recyclerview中?

我不想有一个上帝碎片     viewModel.getParents()。observe(...){parentAdapter.update(it)}和 也必须做某种viewModel.getChildren()。observe(...){parentAdapter。 updateChildren(it)

因为这会破坏关注点分离。在我看来,父级recyclerview中的每个项目都应该有一个视图模型,该视图模型可以获取属于该子级的子项,然后创建一个recyclerview并使用ChildAdapter来显示这些子项,但是我似乎无法弄清楚在哪里插入ChildFragment和ChildViewModel(其中包含repository.getChildrenByParentId)可以正常工作。

我在网上发现的所有示例似乎都无济于事,因为它们使用了没有LiveData且没有将所有内容置于单个适配器中的God片段/活动的人为设计的示例。

1 个答案:

答案 0 :(得分:0)

我实际上会使用1个DiffUtil(或其异步版本)类来呈现所有内容的适配器,以确保我不(且引用)“ 仅因为其中一个属性重绘所有内容一个ChildModel更改”。

我会将构建(和提供)数据这一复杂的职责转移到您的存储库中(或者,如果您更希望将其靠近,则将您的ViewModel充当1个或多个之间的协调者(我不知道如何您的模型看起来不错,所以我只是在想象)提供数据的存储库。

这将使您可以向{em> ui 提供更多经过整理的ParentsAndChildren不变列表,而RecyclerView / Adapter的职责突然变得更加简单,显示并绑定正确的每行的视图。您的UI突然变得更快,花费在主线程上的时间更少,甚至可以完全独立于您的Activity / Fragment进行单元测试逻辑来创建此列表。

我想象ParentsAndChildren是这样的:

class ParentChildren(parent: Parent?, children: Children?)

然后,当parent不为null而children为null时,您的绑定可以使一个视图膨胀。当children不为null时,您就知道它是孩子(您也可以包括父对象,这取决于您如何构造此数据)。问题在这里解决了,您的适配器看起来像

class YourAdapter : ListAdapter<ParentChildren, RecyclerView.ViewHolder>(DiffUtilCallback()) {

...

您需要实施DiffUtilCallback()

internal class DiffUtilCallback : DiffUtil.ItemCallback<ParentChildren>() { 及其两种方法(areContentsTheSameareItemsTheSame)。

以及适配器的两种方法:

    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是上述伪示例中的适配器ParentChildren的类型。

据我所知,现在您必须提供此Activity或Fragment来托管此适配器,该列表通过LiveData或无论您喜欢哪种方式来获得。

它可以是将其传递到viewModel和ViewModel内部的MutableLiveData的存储库,从而暴露LiveData<List<ParentChildren>或类似于UI。

天空是极限。

这将改变将这些数据放在一起的复杂性,使其更接近数据的位置以及SQL / Room的功能可以利用的方式来组合和处理此数据,而不管UI需要或想要做什么。

这是我的建议,但基于我对您的项目的了解非常有限。

祝你好运! :)