【问题标题】:How do you retrieve a tree structure using Room and a ViewModel with LiveData for use in RecyclerView containing sub-RecyclerViews?如何使用 Room 和带有 LiveData 的 ViewModel 检索树结构,以便在包含子 RecyclerView 的 RecyclerView 中使用?
【发布时间】:2019-05-24 19:48:45
【问题描述】:

我正在尝试检索一个项目列表,其中每个项目还可以包含 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
)

我现在使用“getRoot”和“getChildren”通过 DAO 检索项目:

@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 访问的,而 Adapter 无法访问 ViewModel,我将如何使用包含相应子项的 LiveData 来提供包含子项及其各自子项等的项?我应该将 Dao 还是 viewModel 传递给适配器。在那种情况下,我如何从适配器内观察 LiveData,因为它不是片段/活动?有没有更好的解决方案?

【问题讨论】:

    标签: android kotlin android-recyclerview tree android-room


    【解决方案1】:

    这不是一个完美的答案,我认为有更好的方法(可能至少使用transformations),但它现在有效。

    我没有使用数据库加载子项,而是简单地观察 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 与 parent 的 id 匹配。

    适配器包含一个“setItems()”方法,该方法目前使用“notifyDataSetChanged()”(不确定 atm 如何正确更新它)。

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

    现在在适配器的 onBindViewHolder 中,我将孩子传播到新的 recyclerView:

    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>() {
    

    【讨论】:

    • 如果您有“@Ignore”注释,则 VieModel 不会保存该字段。你的孩子从哪里来?我有一个类似的问题,但我从 Retrofit 获取数据并且必须将 ist 保存到 Room。如果我像您一样添加外键,我会得到:“错误:无法弄清楚如何将此字段保存到数据库中。您可以考虑为其添加类型转换器”。我(还)不知道问题出在哪里以及我能做什么......
    猜你喜欢
    • 1970-01-01
    • 2019-02-04
    • 2018-02-10
    • 1970-01-01
    • 2019-11-13
    • 2020-03-17
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    相关资源
    最近更新 更多