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
相关产品推荐
相关产品推荐

