Search code examples
androidkotlinandroid-room

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


I would like to create an entity of a table using room in Android Kotlin project.

A code of a class I want to combine to entity:

@Entity(tableName = "bikes")
class Bike(name: String, appearance: Int, id: Int, sensorFid: Int, sensorRid: Int, lowPressThreshF: Int, lowPressThreshR: Int) {

    var appearance: Int = appearance
    var name: String = name
    @Embedded(prefix = "senF_")
    val sensorFront: Sensor = Sensor(sensorFid, lowPressThreshF)
    @Embedded(prefix = "senR_")
    val sensorRear: Sensor = Sensor(sensorRid, lowPressThreshR)
    @PrimaryKey(autoGenerate = true)
    val id: Int = id
}

When I try to compile the code I get an error:

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). Tried the following constructors but they failed to match: Bike(java.lang.String,int,int,int,int,int,int) -> [param:name -> matched field:name, param:appearance -> matched field:appearance, param:id -> matched field:id, param:sensorFid -> matched field:unmatched, param:sensorRid -> matched field:unmatched, param:lowPressThreshF -> matched field:unmatched, param:lowPressThreshR -> matched field:unmatched]

That's the Sensor class I'm embedding:

class Sensor (_id: Int, _lowPressureTh: Int) {

    var id: Int

    var lowPressureTh: Int

    @Ignore
    var pressureBar: Int?

    @Ignore
    var temperatureC: Int?

    @Ignore
    var battery: Byte?

    init {
        id = _id
        lowPressureTh = _lowPressureTh
        pressureBar = null
        temperatureC = null
        battery = null
    }

    constructor(id: Int, lowPressureTh: Int, pressureBar: Int?, temperatureC: Int?, battery: Byte?) : this(id, lowPressureTh)


    @Ignore
    public fun updateMeasurementFromAdvData(advData: List<Long>) {
        pressureBar = decodeInt(advData[0].toInt())
        temperatureC = decodeInt(advData[1].toInt())
        battery = advData[2].toByte()
        Log.d("Moje", "pressure:" + pressureBar.toString())
        Log.d("Moje", "temp:" +temperatureC.toString())
        Log.d("Moje", "bat:" +battery.toString())

    }

    @Ignore
    private fun decodeInt(codedValue: Int) : Int {
        val buffer = ByteBuffer.allocate(4)
        buffer.putInt(codedValue)
        buffer.order(ByteOrder.LITTLE_ENDIAN)
        buffer.flip()
        return buffer.int
    }

    @Ignore
    public fun equalId(id3: Int, id2: Int, id1: Int): Boolean {
        if (id3 <= 0xFF && id2 <= 0xFF && id1 <= 0xFF) {
            return (id == id3.shl(16).or(id2.shl(8).or(id1)))
        }
        else {
            return false
        }
    }

    @Ignore
    fun setToNullData() {
        pressureBar = null
        temperatureC = null
        battery = null
    }
}

For a sensor class I'm getting a similar error:

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). Tried the following constructors but they failed to match: Sensor(int,int) -> [param:_id -> matched field:unmatched, param:_lowPressureTh -> matched field:unmatched] Sensor(int,int,java.lang.Integer,java.lang.Integer,java.lang.Byte) -> [param:id -> matched field:sensorRear > id, param:lowPressureTh -> matched field:sensorRear > lowPressureTh, param:pressureBar -> matched field:unmatched, param:temperatureC -> matched field:unmatched, param:battery -> matched field:unmatched]

My Dao code:

@Dao
interface BikeDao {

    @Insert
    fun insertBike(bike: Bike)

    @Delete
    fun deleteBike(bike: Bike)

    @Query("SELECT * FROM bikes")
    fun selectBikes() : LiveData<List<Bike>>
}

If I remove @Ignore annotations from sensor class, it compiles.

How to make Room process constructors and @Ignore annotations correctly?

So far I've tried moving the initialization of Bike class fields into a secondary constructor or init{} block, the same error. I' ve got through a dozen of post on SO but I just can't get it...


Solution

  • All you properties that correspond with table columns must be in the constructor. If you want a constructor that uses other properties, then it has to be a secondary constructor. For example, you can write your first class like this (I moved id to the front according to convention):

    @Entity(tableName = "bikes")
    data class Bike(
        @PrimaryKey(autoGenerate = true)
        val id: Int = id,
        var appearance: Int,
        var name: String,
        @Embedded(prefix = "senF_")
        val sensorFront: Sensor,
        @Embedded(prefix = "senR_")
        val sensorRear: Sensor
    ) {
    
        constructor(id: Int, name: String, appearance: Int, sensorFid: Int, sensorRid: Int, lowPressThreshF: Int, lowPressThreshR: Int): 
            this(id, name, appearance, Sensor(sensorFid, lowPressThreshF), Sensor(sensorRid, lowPressThreshR))
    }
    

    Room will use the primary constructor. The secondary constructor has the properties you wanted, so that's the one you can use in your own code.

    For the Sensor class, you can eliminate your secondary constructor and use default arguments. This will effectively generate two constructors, one with just the two non-ignored parameters that have no default, and one with all the parameters. Room will use the one with only the non-ignored properties. This also saves a lot of code.

    data class Sensor(
        var id: Int,
        var lowPressureTh: Int,
        @Ignore
        var pressureBar: Int? = null,
        @Ignore
        var temperatureC: Int? = null,
        @Ignore
        var battery: Byte? = null
    ) {
    
        @Ignore
        public fun updateMeasurementFromAdvData(advData: List<Long>) {
            
        // The rest of your class...
    
    }