简体   繁体   中英

Join 2 Table with Android Kotlin Room and display plain data in a single Row in RecycleView

I am experimenting with Room , Live Data and Recycler View in Android Kotlin .

My question is, I am trying to make an expense tracking APP, and I have 2 Table:

  • one for the expense
  • one for expense type

I joined the table as indicated in Room documentation for 1:N relationship .

Example of my table:

**Expense**
*ID = 1
expenseName = MyExpense1
expenseAmount = 100
expenseTypeID = 1*

**ExpenseType**
*ID= 1
ExpenseType= Home Expenses*

**Result expected from JOIN:**
*expenseName = MyExpense1
expenseAmount = 100
expenseType = Home Expenses*

But in this way, when I get the data for recycler view, I get a list that contain:

  • Expense Type Class
  • Expense list of Expense Class

How can I have data as if **I JOINED ** the table? Since my **ExpenseTypeWithExpense **class contains a class and a List of class

Usually I use a **RecycleView **on just one table and it is easy since I have a list of my Entity Class and I can access the single instance with list[position] in my **onBindViewHolder **class

EXPENSE Class

@Entity(
    foreignKeys =[
        ForeignKey(
            entity = ExpenseType::class,
            parentColumns = ["id"],
            childColumns = ["expenseTypeID"]
        )]
)
data class Expense (
    @PrimaryKey(autoGenerate = true)
    val id:Int,
    val expenseName: String,
    val expenseAmount: Double,
    val expenseTypeID:Int
    )

EXPENSE TYPE Class

@Entity(tableName = "expense_type")
data class ExpenseType (
    @PrimaryKey(autoGenerate = true)
    val id:Int,
    val expenseType: String
    )

EXPENSE TYPE Join with EXPENSE Class (as per documentation of joining 1:n table)


data class ExpenseTypeWithExpense (
    @Embedded val expenseType: ExpenseType,
    @Relation(
        parentColumn ="id",
        entityColumn ="id"
    )
    val expense: List<Expense>
    )

My DAO Interface

@Dao
interface ExpenseDao {
    @Insert
    suspend fun insertExpense(expense: Expense)

    @Insert
    suspend fun insertExpenseType(expenseType:ExpenseType)

    @Transaction
    @Query("SELECT * FROM expense_type")
    fun getExpenseWithType():LiveData<List<ExpenseTypeWithExpense>>
}

Expense Repository

class ExpenseRepository(private val expenseDao: ExpenseDao) {
    val readAllData: LiveData<List<ExpenseTypeWithExpense>> = expenseDao.getExpenseWithType()

    suspend fun insertExpense(expense: Expense){
        expenseDao.insertExpense(expense)
    }

    suspend fun insertExpenseType(expenseType: ExpenseType){
        expenseDao.insertExpenseType(expenseType)
    }
}

ExpenseViewModel

class ExpenseViewModel(application: Application):AndroidViewModel(application) {
    val readAllData: LiveData<List<ExpenseTypeWithExpense>>
    private val repository: ExpenseRepository

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

    fun insertExpense(expense: Expense){
        viewModelScope.launch(Dispatchers.IO){
            repository.insertExpense(expense)
        }
    }
    fun insertExpenseType(expenseType: ExpenseType){
        viewModelScope.launch(Dispatchers.IO){
            repository.insertExpenseType(expenseType)
        }
    }

}

My Adapter

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

    private val expenseList = emptyList<ExpenseTypeWithExpense>()

    class MyViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){

    }

    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.itemView.findViewById<TextView>(R.id.expenseName).text =

# **       //currentItem is an instance of ExpenseTypeWithExpense and contains ExpenseType class and an Expense List**
    }

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

}

** //currentItem is an instance of ExpenseTypeWithExpense and contains ExpenseType class and an Expense List** **

I do not Know how to handle this...

When you use @Relation there is no actual JOIN, rather for each parent (the @Embedded) the children are obtained as a list, as you have found.

If you want the true cartesian product then you would use a POJO without the @Relation but rather @Embeded and the query would have the JOIN.

eg

data class ExpenseTypeWithExpense (
    @Embedded val expenseType: ExpenseType,
    @Embedded
    val expense: Expense
)

The query could then be:-

@Query("SELECT * FROM expense_type JOIN expense ON expense_type.id = expense.expenseTypeID;")

However , as the id field is common to both and that rather than objects (ExpenseType and Expense being included in the output) You probably want the POJO to be

  • When Room maps output columns to the fields, if there are any liked named columns the last is mapped.

:-

data class ExpenseTypeWithExpense (
    val idOfExpenseType:Int,
    val expenseType: String,
    val id:Int,
    val expenseName: String,
    val expenseAmount: Double,
    val expenseTypeID:Int
)

And the Query to (to rename the output columns so they can be assigned to the fields) as

@Query("SELECT expense_type.id AS idOfExpenseType, expense_type.expenseType, expense.* FROM expense_type JOIN expense ON expense_type.id = expense.expenseTypeID;;")
  • as you can see the id column of the expense_type has been given a different name to map to the POJO. As * is not being used the expenseType has to be selected. However rather than specify all the columns of the expense table individually,expense.* is used to output all of the columns of the expense table.

If you had expense_type as:-

id  expenseType
1   T1
2   T2
3   T3

and expense as:-

id  expenseName expenseAmount   expenseTypeID
1   EXP1        100.11          1
2   EXP2        200.22          1
3   EXP3        300.33          1
4   EXP4        400.44          2
5   EXP5        500.55          2

Then the cartesian product output would be:-

在此处输入图像描述

which would equate to 5 ExpenseTypeWithExpense objects being returned in the LiveData<List>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM