Как получить древовидную структуру, используя Room и ViewModel с LiveData для использования в RecyclerView, содержащем под-RecyclerViews? - PullRequest
0 голосов
/ 24 мая 2019

Я пытаюсь получить список элементов, каждый из которых может также содержать вложенные элементы (древовидную структуру) в базе данных Room.Я хочу использовать эти элементы в RecyclerView, который, в зависимости от того, есть ли у элемента субэлементы, сам порождает другой RecyclerView для хранения этих субэлементов.Проблема у меня заключается в том, как правильно получить эти элементы как LiveData.В базе данных у каждого элемента есть parent_id, который указывает на родителя элемента.

@Entity(
    tableName = "items_table",
    foreignKeys = [ForeignKey(entity = Item::class, 
                              parentColumns = ["id"], 
                              childColumns = ["parent_id"], 
                              onDelete = ForeignKey.CASCADE)])
data class Item(
    @PrimaryKey(autoGenerate = true)
    var id: Int,
    var parent_id: Int?,
    var name: String
    ... // Additional fields
)

Теперь я получаю элементы с помощью DAO, используя 'getRoot' и 'getChildren':

@Dao
interface ItemsDao{
    @Query("SELECT * FROM items_table WHERE parent_id=:parent_id")
    fun findChildItems(parent_id: Int): LiveData<List<Item>>

    @Transaction
    @Query("SELECT * FROM items_table WHERE parent_id IS NULL")
    fun getRoot(): LiveData<Item>
}

Элементы и подэлементы теперь должны использоваться с использованием адаптера для RecyclerView

class ItemListAdapter(...) : RecyclerViewAdapter<ItemViewHolder> {

override fun getItemViewType(someItem): Int {
    return when someItem.hasChildren {
        true -> 0
        false -> 1
    }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder{
    val layoutInflater = LayoutInflater.from(parent.context)
     when (viewType) {
        0 -> { // Item has children
            val inflated = // inflated with new recyclerView
            return ItemViewHolder(inflated, 0)
        }
        else -> { // Item has no children
            val inflated = // layout without new recyclerView
            return itemViewHolder(inflated, 1)
        }
    }
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int)
    // [...]
    when (holder.type) {
        0 -> {
            // Customize the view
            holder.itemView.recyclerView.adapter = ItemListAdapter(..., context)
        }
        else -> {
            // Customize the view
        }
    }
}
}

Поскольку доступ к моей базе данных осуществляется с помощью ViewModel, а у адаптера нет доступа к ViewModel, как бы я подалэлементы, содержащие подпункты и их соответствующие подпункты и т. д. с LiveData, связывающими соответствующие дочерние элементы?Должен ли я просто передать Dao или viewModel на адаптер.В таком случае, как мне наблюдать LiveData из Адаптера, так как это не фрагмент / действие?Есть ли лучшее решение?

1 Ответ

0 голосов
/ 25 мая 2019

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

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

Во фрагменте / деятельности:

viewModel = ViewModelProviders.of(this).get(ItemViewModel::class.java)

viewModel.items.observe(this, Observer {items ->
    val rootItem = items.filter { it.parent_id == null }
    for (item in rootItem) //There should be only one root item but filter returns a list
        item.populateChildren(items)
    adapter.setItems(rootItem)
})

Сущности содержат метод populateChildren (subList: List), который рекурсивно заполняет поля @ Ignore'd в элементах, создавая древовидную структуру, начиная с rootItem и доступ к которой осуществляется через его дочерние элементы:

@Entity(
    tableName = "items_table",
    foreignKeys = [ForeignKey(entity = Item::class, parentColumns = ["id"], childColumns = ["parent_id"], onDelete = ForeignKey.CASCADE)])
data class Item(
    @PrimaryKey(autoGenerate = true)
    var id: Int,
    var parent_id: Int?,
    var name: String){
    @Ignore var children: List<Item> = emptyList()

    fun populateChildren(items: List<Item>){
        children = items.filter { it.parent_id == this.id }
        for (child in children)
            child.populateChildren(items)
    }

}

Этот метод просто делает то, что база данных сделала с помощью 'findChildItems ()', и сопоставляет parentID элементов в списке с идентификатором родителя.

Адаптер содержит метод setItems (), который пока использует notifyDataSetChanged () (не уверен, как правильно это обновить).

internal fun setItems(sItems: List<Item>) {
        subList = sItems
        notifyDataSetChanged()
    }

Теперь в onBindViewHolder адаптера я передаю дочерние элементы новому переработчику. View:

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val viewItem = holder.itemView
    val item : Item = subList[position]
    when (holder.type) {
        0 -> { // has children
            // Update viewItem
            // viewItem.[...] = item.[...]
            viewItem.recyclerView.layoutManager =
                LinearLayoutManager(context)
            viewItem.recyclerView.adapter = ItemAdapter(item.children, context)
        }
        else -> { // no Children
            val viewItem = holder.itemView
            // Update viewItem
            // viewItem.[...] = item.[...]
        }
    }
}

Список адаптеров также может быть установлен при создании экземпляра:

class ItemAdapter(var subList: List<Item>, val context: Context) : RecyclerView.Adapter<ItemViewHolder>() {
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...