You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin Room联表后在RecyclerView单行展示数据问题

解决方案

问题根源

你当前用@Relation实现的是以费用类型为主体,关联其下所有费用的1:N分组查询,但你需要的是每条费用记录关联对应的费用类型名称,也就是反过来以费用为主体的关联查询。


方案一:直接通过SQL JOIN查询返回自定义数据类

这种方式最接近你熟悉的SQL联表查询,直接返回包含所需字段的单条数据对象。

1. 创建自定义数据类(DTO)

定义一个包含所有需要字段的数据类,Room会自动映射查询结果:

data class ExpenseWithTypeDto(
    val expenseName: String,
    val expenseAmount: Double,
    val expenseType: String
)

2. 修改DAO接口

添加JOIN查询方法,直接关联两张表并返回上述DTO:

@Dao
interface ExpenseDao {
    // 保留原有插入方法...

    @Query("""
        SELECT e.expenseName, e.expenseAmount, et.expenseType 
        FROM Expense e 
        JOIN expense_type et ON e.expenseTypeID = et.id
    """)
    fun getAllExpensesWithType(): LiveData<List<ExpenseWithTypeDto>>
}

3. 更新Repository和ViewModel

  • Repository:
class ExpenseRepository(private val expenseDao: ExpenseDao) {
    // 替换原有readAllData
    val readAllData: LiveData<List<ExpenseWithTypeDto>> = expenseDao.getAllExpensesWithType()

    // 保留原有插入方法...
}
  • ViewModel:
class ExpenseViewModel(application: Application): AndroidViewModel(application) {
    val readAllData: LiveData<List<ExpenseWithTypeDto>>
    private val repository: ExpenseRepository

    init {
        val expenseDao: ExpenseDao = ExpenseDatabase.getDatabase(application).expenseDao()
        repository = ExpenseRepository(expenseDao)
        readAllData = repository.readAllData
    }

    // 保留原有插入方法...
}

4. 修改RecyclerView Adapter

现在Adapter可以直接处理单条费用数据:

class ListAdapter(): RecyclerView.Adapter<ListAdapter.MyViewHolder>() {

    private var expenseList = emptyList<ExpenseWithTypeDto>()

    class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
        // 提前绑定View,避免重复findViewById
        val tvExpenseName: TextView = itemView.findViewById(R.id.expenseName)
        val tvExpenseAmount: TextView = itemView.findViewById(R.id.expenseAmount)
        val tvExpenseType: TextView = itemView.findViewById(R.id.expenseType)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.custom_row, parent, false)
        )
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = expenseList[position]
        holder.tvExpenseName.text = currentItem.expenseName
        holder.tvExpenseAmount.text = currentItem.expenseAmount.toString()
        holder.tvExpenseType.text = currentItem.expenseType
    }

    override fun getItemCount(): Int {
        return expenseList.size
    }

    // 添加更新数据的方法,用于观察LiveData时刷新列表
    fun setData(newList: List<ExpenseWithTypeDto>){
        expenseList = newList
        notifyDataSetChanged()
    }
}

方案二:用Room的@Relation实现单条费用关联类型

如果想保留Room的实体关联特性,也可以定义以Expense为主体的关联类:

1. 创建ExpenseWithType关联类

data class ExpenseWithType(
    @Embedded val expense: Expense,
    @Relation(
        parentColumn = "expenseTypeID",
        entityColumn = "id"
    )
    val expenseType: ExpenseType
)

2. 修改DAO接口

@Dao
interface ExpenseDao {
    // 保留原有插入方法...

    @Transaction
    @Query("SELECT * FROM Expense")
    fun getAllExpensesWithType(): LiveData<List<ExpenseWithType>>
}

3. 后续更新逻辑

Repository和ViewModel的修改逻辑与方案一一致,Adapter中可以通过currentItem.expense.expenseName、currentItem.expenseType.expenseType来获取对应字段。


额外提示

  • 原代码中ExpenseTypeWithExpense的@Relation参数错误:entityColumn应该是expenseTypeID(Expense表的外键字段),而非id,这会导致关联查询失败,注意修正。
  • 建议在Adapter中使用DiffUtil代替notifyDataSetChanged(),提升列表刷新性能。

内容的提问来源于stack exchange,提问作者Stefano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 23:03:07