Вложенные Recyclerviews с комплексной комнатой LiveData - PullRequest
0 голосов
/ 03 июля 2019

У меня есть коллекция родительских объектов, каждый из которых имеет коллекцию дочерних объектов. Назовите эти ParentModel с и ChildModel с.

На экране я хочу отобразить RecyclerView визуализированных ParentModels, каждый из которых содержит, в частности, RecyclerView визуализированных ChildModels.

Желая не иметь бога LiveData, который перерисовывает все только потому, что изменяется одно свойство одной ChildModel, я намерен разделить их.

Я не могу понять, как структурировать это с помощью адаптеров и держателей 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 правильно, основанная на базовых данных. Но этот родительский просмотрщик также должен:

  1. выборка детей на основе одного parent.id
  2. создать дочернее представление переработчика для элемента родительского просмотра, отображающего дочерние элементы

Room возвращает LiveData> из функции repository.getChildrenByParentId (id: Long). Это данные, с которыми я работаю.

Но где я могу получить это, как мне подключить его к соответствующему дочернему представлению переработчика, которое принадлежит родительскому представлению переработчика?

Я не хочу иметь фрагмент Бога, который делает viewModel.getParents (). наблюдаем (...) {parentAdapter.update (it)} и также должны сделать что-то вроде viewModel.getChildren (). Наблюдать (...) {parentAdapter.updateChildren (это)}

потому что это разрушает разделение интересов. Мне кажется, что у каждого элемента в родительском представлении реселлера должна быть модель представления, которая выбирает дочерние элементы, которые ему принадлежат, затем создает представление рециркулятора и использует ChildAdapter для отображения этих потомков, но я не могу понять, куда подключить ChildFragment и ChildViewModel (с repository.getChildrenByParentId в нем), чтобы все это работало.

Все примеры, которые я нахожу в Интернете, похоже, не помогают, поскольку они используют надуманные примеры без LiveData и фрагмента / действия Бога, который помещает все в один адаптер.

1 Ответ

0 голосов
/ 03 июля 2019

Я бы буквально имел 1 адаптер, который может рендерить все, используя класс DiffUtil (или его асинхронную версию), чтобы гарантировать, что я не (и я цитирую) " перерисовываю все только потому, что одно свойство одного ChildModelизменения ».

Я бы перенес эту сложную обязанность по построению (и предоставлению) данных в ваш репозиторий (или, если вы предпочитаете, чтобы он был ближе, к вашей ViewModel, выступающей в качестве координатора между 1 или более (я неЯ не знаю, как выглядит ваша модель, поэтому я только воображаю) репозитории, предоставляющие данные.

Это позволит вам предложить ui гораздо более курируемый неизменный список ParentsAndChildren вместе иответственность вашего RecyclerView / Adapter неожиданно становится намного проще, отобразите это и привяжите правильное представление для каждой строки. Ваш пользовательский интерфейс неожиданно быстрее, тратит гораздо меньше времени на выполнение задач в главном потоке, и вы даже можете тестировать логику для создания этого списка, полностью независимый от вашей Деятельности / Фрагмента.

Я предполагаю, что ParentsAndChildren будет выглядеть примерно так:

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

Ваша привязка может затем раздувать одно представление, когда родитель не нулевойи дети есть. Когда дети не нуль, вы знаете, что это дети (вы могли бы также включить родителял, зависит от того, как вы строите эти данные).Проблема решена здесь, ваш адаптер будет выглядеть так:

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 - это тип адаптера ParentChildren в приведенном выше псевдо-Пример.

Это все, что я знаю, и теперь вы должны предоставить эту активность или фрагмент, содержащий этот адаптер, список через LiveData или , независимо от того, что вы предпочитаете дляАрхитектура у вас есть.

Это может быть репозиторий, передающий его в MutableLiveData внутри viewModel и ViewModel, отображая LiveData<List<ParentChildren> или похожий на пользовательский интерфейс.

Небо - это предел.

Это сдвигает сложность объединения этих данных, приближает их к месту, где мощь SQL / Room может использовать, как вы комбинируете и обрабатываете это, независимо от того, что пользовательский интерфейс хочет или хочет сделатьс этим.

Это мое предложение, но оно основано на очень ограниченных знаниях, которые у меня есть о вашем проекте.

Удачи!:)

...