Android Room "Entities and POJOs must have a usable public constructor"

Viewed 101

I have 3 entities:

product_table

@Entity(tableName = "products_table")
data class Product (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val name: String,
    val price: Double
)

planned_lists_table

@Entity(tableName = "planned_lists_table")
data class PlannedList (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val name: String,
    val budget: Double
)

and selected_products_table

@Entity(tableName = "selected_products_table",
    foreignKeys = [
        ForeignKey(
            entity = PlannedList::class,
            parentColumns = ["id"],
            onDelete = ForeignKey.CASCADE,
            childColumns = ["productListId"])
    ])
data class SelectedProduct (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val productListId: Int,
    @Embedded(prefix = "ref_") val product: Product,
    val amount: Int,
    val checked: Boolean   // checked if a user has gotten a product
)

I'm trying to figure out how to fetch data from both tables - product_table and selected_products_table. I've chosen @Embedded annotation, but now when I compile the project, Room says next: Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - kotlin.Unit I'm aware that I can't use embedded classes that contain something like List and my tables seem not to have any. What do I need to do to fix the problem?

1 Answers

It appears that you are getting mixed up with the selected_products_table.

I believe that what you are looking for is like (see note) :-

@Entity(tableName = "selected_products_table",
    foreignKeys = [
        ForeignKey(
            entity = Product::class,
            parentColumns = ["id"],
            onDelete = ForeignKey.CASCADE,
            childColumns = ["productListId"])
    ])
data class SelectedProduct (
    @PrimaryKey(autoGenerate = true) val id: Int,
    @ColumnInfo(index = true) val productListId: Int,
    /* you get the product via the join/relationship according to the productListId*/
    //@Embedded(prefix = "ref_") val product: Product, /* you get the product via a join*/
    val amount: Int,
    val checked: Boolean   // checked if a user has gotten a product
)
  • @ColumnInfo added to add an index on the ProductListId column

  • comments explain why you don't want the embedded here.

  • Note it is unclear whether you are relating to a PlannedList or a Product so Product has been used (easy to switch)

You probably want to present the list including the products details.

To extract this data you then have a POJO for the joined/related data, such as:-

data class SelectedProductList(
    @Embedded
    val selectedProduct: SelectedProduct,
    @Relation(entity = Product::class,parentColumn = "productListId",entityColumn = "id")
    val product: Product
)

This is what undertakes the JOIN and populates the product with the data from the products_table.

As an example using your code, the above code and the following :-

    dao.insert(Product(id = 1,name = "Apples", price = 30.55))
    dao.insert(Product(id = 2,name = "Pears", price = 25.70))
    dao.insert(Product(id = 3,"Oranges",price = 47.23))

    dao.insert(SelectedProduct(1,1,25,false))
    dao.insert(SelectedProduct(2,3,10,true))

    for (spl: SelectedProductList in dao.getSelectedList()) {
        Log.d(
            "DBINFO",
            "Product is ${spl.product.name} " +
                    "price is ${spl.product.price} " +
                    "Order is for ${spl.selectedProduct.amount} " +
                    "cost is ${spl.selectedProduct.amount * spl.product.price} " +
                    "Checked is ${spl.selectedProduct.checked}"
        )
    }

The the result output to the log is :-

D/DBINFO: Product is Apples price is 30.55 Order is for 25 cost is 763.75 Checked is false
D/DBINFO: Product is Oranges price is 47.23 Order is for 10 cost is 472.29999999999995 Checked is true
Related