How to implement inheritance for a Room entity (Android, Kotlin) - android

I'm new to Room and haven't find any posts related to this.
I have two classes: BaseModel and ChildModel. ChildModel is a Room entity class, but BaseModel - isn't. How do I extend BaseModel from ChildModel properly? I tried to do it as in the code bellow...
BaseModel.kt:
#Parcelize
open class BaseModel(
open val id: String,
open var name: String,
open var creator: String,
open var dateOfLastEdit: Timestamp,
open var contributors: List<String>,
open var color: Int,
open var offline: Boolean
) : Parcelable {...}
ChildModel.kt (empty constructor used for firebase):
#Entity
#Parcelize
data class ChildModel(
#PrimaryKey(autoGenerate = false)
override val id: String = "",
override var name: String = "",
override var creator: String = "",
#TypeConverters(DateTypeConverter::class)
override var dateOfLastEdit: Timestamp = currentTime(),
#TypeConverters(ListStringConverter::class)
override var contributors: List<String> = emptyList(),
override var color: Int = NO_COLOR,
override var offline: Boolean = false,
var text: String = "",
var lastEditTextPosition: Int = 0,
var lastEditNamePosition: Int = 0,
var lastUserEdited: String = ""
) : Parcelable, BaseModel(id, name, creator, dateOfLastEdit, contributors, color, offline) {
constructor() : this("", "", "", currentTime(), emptyList(), NO_COLOR, false, "", 0, 0, "")
...
}
... but it gives error: "Field has non-unique column name" for all the fields which are overridden from BaseModel. Btw, when create BaseModel as an interface it builds successfully.
My database class:
#Database(entities = [ChildModel::class], version = 1)
#TypeConverters(DateTypeConverter::class, ListStringConverter::class)
abstract class CustomDatabase : RoomDatabase() {
abstract fun childModelDao(): ChildModelDao
companion object {
var INSTANCE: CustomDatabase? = null
fun getDatabase(context: Context): CustomDatabase? {
if (INSTANCE == null) {
synchronized(CustomDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
CustomDatabse::class.java,
DATABASE
).build()
}
}
return INSTANCE
}
fun destroyDatabase() {
INSTANCE = null
}
}
}
The error is not about upgrading database version, because I deleted the previous one.
Thanks in advance!

Ok, it seems that Room somehow creates two columns with same names (for parent class and for child class) I tried adding #Ignore annotation above every field inside BaseModel class. It fixed the issue, the project builds successfully, but I'm not sure that's the best approach.

In cases where an entity inherits fields from a parent entity, it's
usually easier to use the ignoredColumns property of the #Entity
attribute
Source:
https://developer.android.com/training/data-storage/room/defining-data
The example in the article shows an #Entity data class inheriting from an open class.
Based on #Anastasia Dronina's answer, it sounds like the reason to use the ignoredColumns property of the #Entity attribute is because Room will duplicate columns corresponding to all the superclass fields.

Related

How do I create a new record in Android Room database?

This seems like a lazy question, but the offical docs literally don't explain how to do this.
https://developer.android.com/training/data-storage/room
I have an entity like so:
#Entity
data class Shader(
#PrimaryKey(autoGenerate = true) val uid: Int,
val name: String,
val shaderMainText: String,
val paramsJson: String?
)
And some methods on my Dao:
#Insert
fun insertAll(vararg shaders: Shader)
#Delete
fun delete(shader: Shader)
#Update
fun update(shader: Shader)
How do I make a new record and insert it?
I've gotten so far as this:
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
But it complains that I'm missing the argument uid. Why do I need to provide a uid? It's supposed to be auto-generated?
An example of creating a new record would be appreciated.
You can set 0 for the default value of your uid field
#Entity
data class Shader(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
val name: String,
val shaderMainText: String,
val paramsJson: String?
)
When autoGenerate is true, Room ignores the default value, so it creates auto-incremented values.
Finally, this doesn't give you a compilation error:
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
The docs say:
If the field type is long or int (or its TypeConverter converts it to a long or int), Insert methods treat 0 as not-set while inserting the item.
If the field's type is Integer or Long (or its TypeConverter converts it to an Integer or a Long), Insert methods treat null as not-set while inserting the item.
So I would try setting the uid to be initialized to 0 or make it nullable and set to null.
Hope that helps!
https://developer.android.com/reference/android/arch/persistence/room/PrimaryKey#autoGenerate()
You Can Also try like this
The best practice is you always create an instance Class
1st Create a Database instance class
#Database(entities = [Shader::class], version = 1)
abstract class DatabaseHelper : RoomDatabase() {
companion object {
private var instance: DatabaseHelper? = null
fun getInstance(context: Context?): DatabaseHelper? {
if (instance == null) {
synchronized(DatabaseHelper::class) {
if (context != null) {
instance = Room.databaseBuilder(context.applicationContext, DatabaseHelper::class.java, "database.db").allowMainThreadQueries().build()
}
}
}
return instance
}
}
abstract fun getDao(): DBDao?
}
Create Your Shader data class like below
#Entity(tableName = "shader")
data class Shader(
#PrimaryKey(autoGenerate = true) var id: Int?,
val name: String,
val shaderMainText: String,
val paramsJson: String?
) {
constructor(name: String, shaderMainText: String, paramsJson: String?) : this(
null,
name,
shaderMainText,
paramsJson
)
}
Here you want to create constructor
Add data in Shader and insertAll Like Below
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
DatabaseHelper.getInstance(this)?.getDao()?.insertAll(record)
And Your Data added is successfully
There should be always a #PrimaryKey(autoGenerate = false) while inserting data in the DB

Android Room, how to save an entity with one of the variables being a sealed class object

I want to save in my Room database an object where one of the variables can either be of on type or another. I thought a sealed class would make sense, so I took this approach:
sealed class BluetoothMessageType() {
data class Dbm(
val data: String
) : BluetoothMessageType()
data class Pwm(
val data: String
) : BluetoothMessageType()
}
Or even this, but it is not necessary. I found that this one gave me even more errors as it did not know how to handle the open val, so if I find a solution for the first version I would be happy anyway.
sealed class BluetoothMessageType(
open val data: String
) {
data class Dbm(
override val data: String
) : BluetoothMessageType()
data class Pwm(
override val data: String
) : BluetoothMessageType()
}
Then the Entity class
#Entity(tableName = MESSAGES_TABLE_NAME)
data class DatabaseBluetoothMessage(
#PrimaryKey(autoGenerate = true)
val id: Long = 0L,
val time: Long = Instant().millis,
val data: BluetoothMessageType
)
I have created a TypeConverter to convert it to and from a String as well, so I assume that it is not a problem.
First, is this possible? I assume this should function in a similar way that it would with an abstract class, but I have not managed to find a working solution with that either. If it is not possible, what sort of approach should I take when I want to save some data that may be either of one or another type if not with sealed classes?
We faced such problem when we tried using Polymorphism in our domain, and we solved it this way:
Domain:
We have a Photo model that looks like this:
sealed interface Photo {
val id: Long
data class Empty(
override val id: Long
) : Photo
data class Simple(
override val id: Long,
val hasStickers: Boolean,
val accessHash: Long,
val fileReferenceBase64: String,
val date: Int,
val sizes: List<PhotoSize>,
val dcId: Int
) : Photo
}
Photo has PhotoSize inside, it looks like this:
sealed interface PhotoSize {
val type: String
data class Empty(
override val type: String
) : PhotoSize
data class Simple(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val size: Int,
) : PhotoSize
data class Cached(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val bytesBase64: String,
) : PhotoSize
data class Stripped(
override val type: String,
val bytesBase64: String,
) : PhotoSize
}
Data:
There is much work to do in our data module to make this happen. I will decompose the process to three parts to make it look easier:
1. Entity:
So, using Room and SQL in general, it is hard to save such objects, so we had to come up with this idea. Our PhotoEntity (Which is the Local version of Photo from our domain looks like this:
#Entity
data class PhotoEntity(
// Shared columns
#PrimaryKey
val id: Long,
val type: Type,
// Simple Columns
val hasStickers: Boolean? = null,
val accessHash: Long? = null,
val fileReferenceBase64: String? = null,
val date: Int? = null,
val dcId: Int? = null
) {
enum class Type {
EMPTY,
SIMPLE,
}
}
And our PhotoSizeEntity looks like this:
#Entity
data class PhotoSizeEntity(
// Shared columns
#PrimaryKey
#Embedded
val identity: Identity,
val type: Type,
// Simple columns
#Embedded
val locationLocal: LocalFileLocation? = null,
val width: Int? = null,
val height: Int? = null,
val size: Int? = null,
// Cached and Stripped columns
val bytesBase64: String? = null,
) {
data class Identity(
val photoId: Long,
val sizeType: String
)
enum class Type {
EMPTY,
SIMPLE,
CACHED,
STRIPPED
}
}
Then we have this compound class to unite PhotoEntity and PhotoSizeEntity together, so we can retrieve all data required by our domain's model:
data class PhotoCompound(
#Embedded
val photo: PhotoEntity,
#Relation(entity = PhotoSizeEntity::class, parentColumn = "id", entityColumn = "photoId")
val sizes: List<PhotoSizeEntity>? = null,
)
2. Dao
So our dao should be able to store and retrieve this data. You can have two daos for PhotoEntity and PhotoSizeEntity instead of one, for the sake of flexibility, but in this example we will use a shared one, it looks like this:
#Dao
interface IPhotoDao {
#Transaction
#Query("SELECT * FROM PhotoEntity WHERE id = :id")
suspend fun getPhotoCompound(id: Long): PhotoCompound
#Transaction
suspend fun insertOrUpdateCompound(compound: PhotoCompound) {
compound.sizes?.let { sizes ->
insertOrUpdate(sizes)
}
insertOrUpdate(compound.photo)
}
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entity: PhotoEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entities: List<PhotoSizeEntity>)
}
3. Adapter:
After solving the problem of saving data to SQL database, we now need to solve the problem of converting between domain and local entities. Our Photo's converter aka adapter looks like this:
fun Photo.toCompound() = when(this) {
is Photo.Empty -> this.toCompound()
is Photo.Simple -> this.toCompound()
}
fun PhotoCompound.toModel() = when (photo.type) {
PhotoEntity.Type.EMPTY -> Photo.Empty(photo.id)
PhotoEntity.Type.SIMPLE -> this.toSimpleModel()
}
private fun PhotoCompound.toSimpleModel() = photo.run {
Photo.Simple(
id,
hasStickers!!,
accessHash!!,
fileReferenceBase64!!,
date!!,
sizes?.toModels()!!,
dcId!!
)
}
private fun Photo.Empty.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.EMPTY
)
return PhotoCompound(photo)
}
private fun Photo.Simple.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.SIMPLE,
hasStickers = hasStickers,
accessHash = accessHash,
fileReferenceBase64 = fileReferenceBase64,
date = date,
dcId = dcId,
)
val sizeEntities = sizes.toEntities(id)
return PhotoCompound(photo, sizeEntities)
}
And for the PhotoSize, it looks like this:
fun List<PhotoSize>.toEntities(photoId: Long) = map { photoSize ->
photoSize.toEntity(photoId)
}
fun PhotoSize.toEntity(photoId: Long) = when(this) {
is PhotoSize.Cached -> this.toEntity(photoId)
is PhotoSize.Empty -> this.toEntity(photoId)
is PhotoSize.Simple -> this.toEntity(photoId)
is PhotoSize.Stripped -> this.toEntity(photoId)
}
fun List<PhotoSizeEntity>.toModels() = map { photoSizeEntity ->
photoSizeEntity.toModel()
}
fun PhotoSizeEntity.toModel() = when(type) {
PhotoSizeEntity.Type.EMPTY -> this.toEmptyModel()
PhotoSizeEntity.Type.SIMPLE -> this.toSimpleModel()
PhotoSizeEntity.Type.CACHED -> this.toCachedModel()
PhotoSizeEntity.Type.STRIPPED -> this.toStrippedModel()
}
private fun PhotoSizeEntity.toEmptyModel() = PhotoSize.Empty(identity.sizeType)
private fun PhotoSizeEntity.toCachedModel() = PhotoSize.Cached(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
bytesBase64!!
)
private fun PhotoSizeEntity.toSimpleModel() = PhotoSize.Simple(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
size!!
)
private fun PhotoSizeEntity.toStrippedModel() = PhotoSize.Stripped(
identity.sizeType,
bytesBase64!!
)
private fun PhotoSize.Cached.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.CACHED,
locationLocal = location.toEntity(),
width = width,
height = height,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Simple.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.SIMPLE,
locationLocal = location.toEntity(),
width = width,
height = height,
size = size
)
private fun PhotoSize.Stripped.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.STRIPPED,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Empty.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.EMPTY
)
That's it!
Conclusion:
To save a sealed class to Room or SQL, whether as an Entity, or as an Embedded object, you need to have one big data class with all the properties, from all the sealed variants, and use an Enum type to indicate variant type to use later for conversion between domain and data, or for indication in your code if you don't use Clean Architecture. Hard, but solid and flexible. I hope Room will have some annotations that can generate such code to get rid of the boilerplate code.
PS: This class is taken from Telegram's scheme, they also solve the problem of polymorphism when it comes to communication with a server. Checkout their TL Language here: https://core.telegram.org/mtproto/TL
PS2: If you like Telegram's TL language, you can use this generator to generate Kotlin classes from scheme.tl files: https://github.com/tamimattafi/mtproto
EDIT: You can use this code generating library to automatically generate Dao for compound classes, to make it easier to insert, which removes a lot of boilerplate to map things correctly.
Link: https://github.com/tamimattafi/android-room-compound
Happy Coding!
In my case I did the following:
sealed class Status2() {
object Online : Status2()
object Offline : Status2()
override fun toString(): String {
return when (this) {
is Online ->"Online"
is Offline -> "Offline"
}
}
}
class StatusConverter{
#TypeConverter
fun toHealth(value: Boolean): Status2 {
return if (value){
Status2.Online
} else{
Status2.Offline
}
}
#TypeConverter
fun fromHealth(value: Status2):Boolean {
return when(value){
is Status2.Offline -> false
is Status2.Online -> true
}
}
}
#Dao
interface CourierDao2 {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStatus(courier: CourierCurrentStatus)
#Query("SELECT * FROM CourierCurrentStatus")
fun getCourierStatus(): Flow<CourierCurrentStatus>
}
#Entity
data class CourierCurrentStatus(
#PrimaryKey
val id: Int = 0,
var status: Status2 = Status2.Offline
)
and it works like a charm

How to store Kotlin Enum with Room using Gson in TypeConverter?

I faced problems trying to save Kotlin Enum with Room as JSON (using Gson). I've completely followed official Google instruction and add TypeConverter, but it keeps giving an error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
My entity class:
#Entity(tableName = TextHighlight.TABLE_NAME)
data class TextHighlight.TABLE_NAME(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = COLUMN_ID)
var id: Long = 0,
#TypeConverters(HighlightingColorConverter::class)
#ColumnInfo(name = COLUMN_HIGHLIGHT)
var color: HighlightingColor
) {
My Kotlin Enum class:
enum class HighlightingColor(
#SerializedName("rgb")
var rgb: String,
#SerializedName("title")
var title: String
)
My TypeConverter:
class HighlightingColorConverter {
#TypeConverter
fun fromHighlightingColor(highlight: HighlightingColor) = Gson().toJson(highlight)
#TypeConverter
fun toHighlightingColor(s: String): HighlightingColor =
Gson().fromJson(s, HighlightingColor::class.java)
}
Can't understand what's wrong. Please, help to figure it out!
After sometime I've figured how to fix it.
TypeConverter functions should be placed in companion object and has #JvmStatic annotation. It sound logical because these functions should be static in java.
#TypeConverters annotation should be placed not above entity filed, but above hole entity data class.
Final code is:
#TypeConverters(TextHighlight.HighlightingColorConverter::class)
#Entity(tableName = TextHighlight.TABLE_NAME)
data class TextHighlight(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = COLUMN_ID)
var id: Long = 0,
#ColumnInfo(name = COLUMN_HIGHLIGHT)
var color: HighlightingColor
) {
class HighlightingColorConverter {
companion object {
#JvmStatic
#TypeConverter
fun fromHighlightingColor(highlight: HighlightingColor) = Gson().toJson(highlight)
#JvmStatic
#TypeConverter
fun toHighlightingColor(s: String): HighlightingColor =
Gson().fromJson(s, HighlightingColor::class.java)
}
}
}

Room - Android. How to request data from multiple entities?

I have 4 different entities with the same type of data..
class {
val Int
val String
val String
}
I am using ViewModel to request the data and right now I have for Observables which updates the list adapter.
tagsViewModel.getAllText().observe(this,
Observer<List<Text>> { t -> adapter.setTags(t!!) })
My problem is that I am having some troubles when updating the ui so I wanted to just do one request to get the 4 different types of entities but I don't know how to get one only list with all different classes.
This is a class type
#Entity(tableName = "text")
data class Text(override var content: String, override var date: Long, override var type: String = AppConstants.TYPE_TEXT) : BaseTag() {
#PrimaryKey(autoGenerate = true)
override var id: Int = 0
}
and Base interface
abstract class BaseTag {
abstract val content: String?
abstract val date: Long?
abstract val id: Int?
abstract val type: String?
}
I would like to do this request:
{ texts : [Text,Text,Text],
emails : [Email,Email,Email]...
}
So... Is there any simple way to do this?
Thanks for any help.

Room Persistence: Error:Entities and Pojos must have a usable public constructor

I'm converting a project to Kotlin and I'm trying to make my model (which is also my entity) a data class
I intend to use Moshi to convert the JSON responses from the API
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
var release_date: String,
var vote_average: Double,
var isFavorite: Int
)
I can't build the app cause of the following 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).
Cannot find setter for field.
The examples I found are not far from this
Ideas on how to solve it?
It's not a problem in your case, but for others, this error can occur if you have #Ignore params in your primary constructor, i.e. Room expects to have either:
parameterless constructor or
constructor with all fields not marked with #Ignore
for example:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
#Ignore var overview: String)
will not work. This will:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String)
Had a similar issue before.
First I've updated/added apply plugin: 'kotlin-kapt' to gradle.
Next, I've used it instead of annotationProcessor in gradle:
kapt "android.arch.persistence.room:compiler:1.0.0-alpha4"
Tha last thing was to create an immutable data class:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
val id : Int,
val title: String,
val overview: String,
val poster_path: String,
val backdrop_path: String,
val release_date: String,
val vote_average: Double,
val isFavorite: Int
)
UPDATE:
This solution works when you have classes for the model and classes for Database in the same Android Module. If you have model classes in Android Library module and the rest of the code in your main module, Room will NOT recognize them.
I had the same issue. You can move the #Ignore fields to class body. For example :
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String
){
//here
#Ignore var overview: String
}
you need to specify a secondary constructor like so:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
var release_date: String,
var vote_average: Double,
var isFavorite: Int
) {
constructor() : this(0, "", "", "", "", "", 0.0, 0)
}
To expand on the answers provided by #evanchooly and #daneejela, you need a secondary constructor to be able to use #Ignore parameters in your primary constructor. This is so Room still has a constructor that it can use when instantiating your object. Per your example, if we ignore one of the fields:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
#Ignore var release_date: String,
#Ignore var vote_average: Double,
#Ignore var isFavorite: Int
) {
constructor(id: Int, title: String, overview: String, poster_path: String, backdrop_path: String)
: this(id, title, overview, poster_path, backdrop_path, "", 0.0, 0)
}
What worked for me:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int? = 0,
var title: String? = "",
var overview: String? = "",
var poster_path: String? = "",
var backdrop_path: String? = "",
var release_date: String? = "",
var vote_average: Double? = 0.0,
var isFavorite: Int? = 0
)
Kotlin allows long as a parameter name, but this won't work when room generates java code.
Today I was having this problem. I used #Ignore, that is why I got the error. To solve this I created a secondary constructor. So my code looks something like this:
#Entity(tableName = "profile")
data class Profile(
#field:SerializedName("id") #PrimaryKey #ColumnInfo(name = "id") var id:Long,
#field:SerializedName("foo") #ColumnInfo(name = "foo") var foo:String?,
#field:SerializedName("bar") #Ignore var Bar:String?
){
constructor(id:Long, foo:String) : this(id, foo, null)
}
This worked for me.
For me all I had to do was to add a constructor to the data class with empty params sent to it like so:
#Entity(tableName = "posts")
data class JobPost(
#Ignore
#SerializedName("companyLogo")
var companyLogo: String,
#Ignore
#SerializedName("companyName")
var companyName: String,
#Ignore
#SerializedName("isAggregated")
var isAggregated: String,
#PrimaryKey(autoGenerate = false)
#SerializedName("jobID")
var jobID: String,
#Ignore
#SerializedName("jobTitle")
var jobTitle: String,
#Ignore
#SerializedName("postedOn")
var postedOn: String,
#Ignore
#SerializedName("region")
var region: String
) {
constructor() : this("","","","","","","")
}
I also had this issue, but i realized the problem was that i added the #Embedded annotation to a property that already had a type converter, so anyone having the same problem should check the property declarations for your model class carefully and make sure the #Embedded annotation is not on a property that has a type converter associated with it.
I spent an hour trying to figure this out with no success. This is what I found. I forgot to add the return type in one of my Queries
this resulted with the POJO error
#Query("SELECT userNote FROM CardObject WHERE identifier = :identifier")
suspend fun getUserNote(identifier: String)
No POJO error
#Query("SELECT userNote FROM CardObject WHERE identifier = :identifier")
suspend fun getUserNote(identifier: String): String
I think that a good option for resolve it is:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int = 0,
var title: String = "",
var overview: String = "",
var poster_path: String = "",
var backdrop_path: String = "",
var release_date: String = "",
var vote_average: Double = 0.0,
var isFavorite: Int = 0
)
For me, I was using 'lat' & 'long' as a variable name in the data(Entity) class for kotlin so renaming to latitude & longitude it worked.
Not working:
#Entity(tableName = "table_User")
data class User(#PrimaryKey var userId : Int, #ColumnInfo(name = "first_name")
var firstName: String
, #ColumnInfo(name = "last_name") var lastName: String
, #ColumnInfo(name = "password") var password: String
, #ColumnInfo(name = "dob") var dob: Long
, #ColumnInfo(name = "address") var address: String
, #ColumnInfo(name = "lat") var latitude: Double
, #ColumnInfo(name = "long") var longitude: Double) {
}
Working:
#Entity(tableName = "table_User")
data class User(#PrimaryKey var userId : Int, #ColumnInfo(name = "first_name")
var firstName: String
, #ColumnInfo(name = "last_name") var lastName: String
, #ColumnInfo(name = "password") var password: String
, #ColumnInfo(name = "dob") var dob: Long
, #ColumnInfo(name = "address") var address: String
, #ColumnInfo(name = "latitude") var latitude: Double
, #ColumnInfo(name = "longitude") var longitude: Double) {
}
I had this problem with an entity (all fields were properly-initialized vars like a lot of the answers here are suggesting) that included a list of related, non-primitive items like the OP in this SO question had. For example:
#Entity(tableName = "fruits")
data class CachedFruitEntity(
#PrimaryKey var id: Long = 0L,
#Embedded(prefix = "buyer_") var buyerEntity: CachedBuyerEntity? = null
#TypeConverters(VendorsConverter::class)
var vendorEntities: List<CachedVendorEntity?> = listOf()))
That is, it has an embedded field, and it took me a while to realize that what I actually needed was a type converter for the vendor entity list instead (the compiler wasn't throwing the usual Error:(58, 31) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. So my solution was very similar to this answer
This google architecture components github thread has more info on this misleading error, but not sure if the issue has been fixed yet.
Like it's said in the Room docs, you are required to make an empty public constructor. At the same time, if you want to declare other custom constructors, you must add #Ignore annotation.
#Entity
public class CartItem {
#PrimaryKey
public int product_id;
public int qty;
public CartItem() {
}
#Ignore
public CartItem(int product_id, int count) {
this.product_id = product_id;
this.qty = count;
}
}
It turned out to be a bug on the library
https://github.com/googlesamples/android-architecture-components/issues/49
https://issuetracker.google.com/issues/62851733
i found this is #Relation's projection bug!
not Kotlin language problem.
based google GithubBrowserSample java also happend error, but different error message.
below is my kotlin code:
data class UserWithCommunities(
#Embedded
var user: User = User(0, null),
#Relation(parentColumn = "id",
entityColumn = "users_id",
entity = CommunityUsers::class,
projection = arrayOf("communities_id")) // delete this line.
var communityIds: List<Int> = emptyList()
)
right:
data class UserWithCommunities(
#Embedded
var user: User = User(0, null),
#Relation(parentColumn = "id",
entityColumn = "users_id",
entity = CommunityUsers::class)
var communityList: List<CommunityUsers> = emptyList()
)
Same bug, much stranger solution: Do not return cursor using reactivex Maybe<Cursor> on your Dao. Flowable, Single, and Observable did not work either.
Simply bite the bullet and make the reactive call outside the Dao request.
Before:
#Dao
interface MyDao{
#Query("SELECT * FROM mydao")
fun getCursorAll(): Flowable<Cursor>
}
After:
#Dao
interface MyDao{
#Query("SELECT * FROM mydao")
fun getCursorAll(): Cursor
}
Meta:
Android Studio 3.2
Build #AI-181.5540.7.32.5014246, built on September 17, 2018
JRE: 1.8.0_152-release-1136-b06 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.12.6
Just add the below annotation to any constructor that causes the errors and add a new blank constructor.
#Ignore
With 2.1.0-alpha6, it turned out to be an invalid return type in Dao. Fixing the return type as expected fixed it.
Kotlin plugin doesn't pick up annotationProcessor dependencies, So use the latest version of Kotlin annotation processor - put this line at top of your module's level build.gradle file
apply plugin: 'kotlin-kapt'
like
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' // add this line
android {
compileSdkVersion 28
defaultConfig {
........
}
}
Don't forget to change the compileSdkVersion accordingly.
I had the same problem and the reason was because the type of data I was getting by query in dao , was not equal to the type of data I was returning.
The type of id in my database was String and I changed the dao from:
#Query("SELECT id FROM content_table")
fun getIds(): Flow<List<Int>>
To :
#Query("SELECT id FROM content_table")
fun getIds(): Flow<List<String>>
For this issue, I had the same problem.
Replace the Room Dependencies with that of the latest one present in the official docs
As stated in Room Database Entity:
Each entity must either have a no-arg constructor or a constructor
whose parameters match fields (based on type and name).
So adding an empty constructor and annotating your parameterized constructor with #Ignore will solve your problem. An example:
public class POJO {
long id;
String firstName;
#Ignore
String lastName;
public POJO() {
}
#Ignore
public POJO(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// getters and setters
// ...
}
make sure room database column name and field name in constructor are same
In my case I had the #Ignore Tags and 'kotlin-kapt' plugin but still this started to happen after updating to kotlin to version 1.5.0.
I ended up updating my room library from version 2.2.5 to 2.3.0 and the problem was fixed.
another problem with
#Entity
data class SomeEnity(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val filed: SomeClass
)
**inline** class SomeClass
consider to remove inline class
If u use Java. Then my solution was to only ADD #Nonull in the constructor
constructor(#Nonull String,
I've been having this error for the longest time. And so I want to give tips to those who are facing the same problem, it may help you.
Add all these dependencies or choose the one you will be using : https://developer.android.com/jetpack/androidx/releases/lifecycle
Make sure while creating your Class (in my case Public Class Message) it implements Serializable (example)
Avoid naming your attr with capital in front, it will be hard for the DAO_impl to detect. If you want to then make sure the getter and setter is also capitalized.
In my case I was using datatype name long as a field name
#Entity(tableName = "MyLocations")
data class MyLocationModel(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
var name: String,
val stored: Boolean,
val lat: Double,
val long: Double
)
just changed long to lon worked for me
#Entity(tableName = "MyLocations")
data class MyLocationModel(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
var name: String,
val stored: Boolean,
val lat: Double,
val lon: Double
)

Categories

Resources