When building my app, I am getting an error with two entity classes. It's basically saying it cannot find the setter for the constructors I am giving. Originally I had vals, I converted these to vars and that seems to fix the issue.
But I don't like this fix... The documentation uses vals in their examples of how to build entities. So why does it not work for certain entities I define? I feel like the coding I'm doing is going to be prone to some sort of error because I am only bandaging the problem rather than actually fixing it.
https://developer.android.com/training/data-storage/room/defining-data
data class DirectMessage(
#PrimaryKey
#ColumnInfo(name = "dm_id") override val objectId: String,
//Author of message
#Ignore override val author: User,
//Recipient of message, usually user of the app
#Ignore override val recipient: User,
//Content of message
override val content: String,
//Date of creation
override val timestamp: Long
) : DirectMessage {
//Place identifier into database instead of Entity
#ColumnInfo(name = "author") val _author : String = author.uid
#ColumnInfo(name = "recipient") val _recipient : String = recipient.uid
/**
* Get author's user thumbnail
*/
override fun getThumbnail() {
TODO("Not yet implemented")
}
}
#Entity
data class Comment (
#PrimaryKey
#ColumnInfo(name = "cid") override val objectId: String,
//Author of the comment
#Ignore override val author: User,
//Moment ID this comment is attached to
override var momentId: String,
//Time comment was created
override val timestamp: Long,
//Content of the comment
override var content: String
) : Comment {
//Place identifier into database instead of User entity
#ColumnInfo(name = "author") val _author = author.uid
/**
* Get thumbnail of User object
*/
override fun getThumbnail() {
TODO("Not yet implemented")
}
}
interface Comment : Message {
val momentId : String
}
public final class Comment implements com.example.barrechat192.data.entities.Comment {
^
Tried the following constructors but they failed to match:
Comment(java.lang.String,com.example.barrechat192.data.entities.User,java.lang.String,long,java.lang.String) -> [param:objectId -> matched field:objectId, param:author -> matched field:unmatched, param:momentId -> matched field:momentId, param:timestamp -> matched field:timestamp, param:content -> matched field:content]C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\Comment.java:13: error: Cannot find setter for field.
private final java.lang.String _author = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\Comment.java:17: error: Cannot find setter for field.
private final java.lang.String objectId = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\Comment.java:23: error: Cannot find setter for field.
private final long timestamp = 0L;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:7: 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).
public final class DirectMessage implements com.example.barrechat192.data.entities.DirectMessage {
^
Tried the following constructors but they failed to match:
DirectMessage(java.lang.String,com.example.barrechat192.data.entities.User,com.example.barrechat192.data.entities.User,java.lang.String,long) -> [param:objectId -> matched field:objectId, param:author -> matched field:unmatched, param:recipient -> matched field:unmatched, param:content -> matched field:content, param:timestamp -> matched field:timestamp]C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:10: error: Cannot find setter for field.
private final java.lang.String _author = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:13: error: Cannot find setter for field.
private final java.lang.String _recipient = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:17: error: Cannot find setter for field.
private final java.lang.String objectId = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:25: error: Cannot find setter for field.
private final java.lang.String content = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\messages\DirectMessage.java:26: error: Cannot find setter for field.
private final long timestamp = 0L;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\implementations\mapobjects\MapObject.java:10: error: The name "map_objects" is used by multiple entities or views: com.example.barrechat192.data.entities.implementations.mapobjects.MapObject, com.example.barrechat192.data.entities.implementations.mapobjects.MapObject
public final class MapObject implements com.example.barrechat192.data.entities.MapObject {
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\AppDatabase.java:8: error: The name "map_objects" is used by multiple entities or views: com.example.barrechat192.data.entities.implementations.mapobjects.MapObject, com.example.barrechat192.data.entities.implementations.mapobjects.MapObject
public abstract class AppDatabase extends androidx.room.RoomDatabase {
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\geocache\GeoTableWithMapObjects.java:12: error: Cannot find the child entity column `geohash` in com.example.barrechat192.data.entities.implementations.mapobjects.MapObject. Options: objectId, timestamp, thumbnailUrl, thumbnailPath, objectType, local, views, viewed, geoHash, latitude, longitude
private final java.util.List<com.example.barrechat192.data.entities.implementations.mapobjects.MapObject> mapObjects = null;
^C:\Users\Anon\AndroidStudioProjects\Barrechat192\app\build\tmp\kapt3\stubs\debug\com\example\barrechat192\data\entities\geocache\GeoTableWithMapObjects.java:6: 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).
I also don't understand why I can inherit a val and change it to a var in a child class in Kotlin. That also seems to make the code work, changing the val to var in the entity class, but leaving it as a val in the interface.
Hi you have created constructors which is used when it is created by you.
You should create constructor passing params all database columns. Room is trying to create an instance but can not find setters for these mapped fields
You Need create setter for your model, the database cannot set the data.
See this: Getter and Setter
Related
I have seen this question several times on SO. however the solution doesn't seem to apply to my problem.
I have a Kotlin data-class that is used as an Entity in Room
#Entity(tableName = "training_session")
data class SessionEntity(
#PrimaryKey(autoGenerate = false) val id: Long,
#ColumnInfo(name = "current_state_marker") val currentState: Short,
#Embedded val states: List<Int>
)
It is producing
> Task :training-infrastructure:kaptDebugKotlin FAILED
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). - java.util.List
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). - java.util.List
In the same project I have a very similar entity which also has a list and that doesn't produce any errors.
Tried out the answer provided by MikeT, for me it required a small change in the way the converters were defined
data class SessionStateList (val stateList : List<Int>)
class SessionStateListConverter {
#TypeConverter
fun fromArraySessionStateList(sh: List<Int>?): String? {
return Gson().toJson(sh)
}
#TypeConverter
fun toArraySessionStateList(sh: String?): List<Int>? {
val listType: Type = object : TypeToken<ArrayList<Int?>?>() {}.type
return Gson().fromJson(sh,listType)
}
}
A quick follow-up. I had mentioned that I have another Entity that has an Embedded val something: List<Int> and I had not noticed any compiler errors.
The reason, I had not noticed any compiler errors was because the entity was not included in the #Database annotation.
You cannot have a List/Array etc as a column type. So your issue is centred on #Embedded val states: List<Int>
You could have a POJO e.g. StatesHolder :-
data class StatesHolder(
val stateList: List<Int>
)
and then have
#Entity(tableName = "training_session")
data class SessionEntity(
#PrimaryKey(autoGenerate = false) val id: Long,
#ColumnInfo(name = "current_state_marker") val currentState: Short,
val states: StatesHolder
)
Note that you cannot Embed StatesHolder as then that just inserts List. If you want to Embed then you have to Embed a wrapper that uses a StatesHolder.
You will then need TypeConverters to convert to and from a StatesHolder object to a type that can be stored. Probably a String and Probably a JSON respresentation of the StatesHold object e.g.
class Converters {
#TypeConverter
fun fromStatesHolder(sh: StatesHolder): String {
return Gson().toJson(sh)
}
#TypeConverter
fun toStatesHolder(sh: String): StatesHolder {
return Gson().fromJson(sh,StatesHolder::class.java)
}
}
You additionally need to use #TypeConverters annotation that defines the Converts::class. If coded at the #Database level the converters have full scope.
So after #Database(.....) you could have :-
#TypeConverters(Converters::class)
I have a Room database Entity called Conversation and I would like to androidx.room.#Ignore the messages member variable. However, when I try to run the app, I get this error.
/db/entity/Conversation.java:10: 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).
public final class Conversation {
^
Tried the following constructors but they failed to match:
Conversation(int,java.util.List<com.example.app.db.entity.Message>) -> [param:id -> matched field:id, param:messages -> matched field:unmatched]/db/entity/Conversation.java:12: error: Cannot find setter for field.
private final int id = 0;
Here is the entity code.
/**
* Entity used to model the conversation SQLite table in the database.
*/
#Entity(tableName = "conversation")
data class Conversation(
#PrimaryKey val id: Int,
#Ignore val messages: List<Message>
)
When I comment out the #Ignore val messages... line, the app compiles.
I've tried both Room database versions 2.2.5 and 2.3.0-alpha03 and both exhibit the same errors.
I was able to fix this issue by converting the entity to a non-data class:
/**
* Entity used to model the conversation SQLite table in the database.
*/
#Entity(tableName = "conversation")
class Conversation constructor(#PrimaryKey val id: Int) {
#Ignore
val messages = mutableListOf<Message>()
}
I am trying to get Room(https://developer.android.com/topic/libraries/architecture/room) work with Kotlin's inline classes as described in Jake Whartons article Inline Classes Make Great Database IDs:
#Entity
data class MyEntity(
#PrimaryKey val id: ID,
val title: String
)
inline class ID(val value: String)
When compiling this Room complains that
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).
Looking into the generated Java code I find:
private MyEntity(String id, String title) {
this.id = id;
this.title = title;
}
// $FF: synthetic method
public MyEntity(String id, String title, DefaultConstructorMarker $constructor_marker) {
this(id, title);
}
Mysteriously the default constructor is private now.
When using String as a type for id (or a typealias), the generated Java class constructor looks like expected:
public MyEntity(#NotNull String id, #NotNull String title) {
Intrinsics.checkParameterIsNotNull(id, "id");
Intrinsics.checkParameterIsNotNull(title, "title");
super();
this.id = id;
this.title = title;
}
Does somebody now how to keep the default constructor public while using Inline Classes as data entity properties?
I believe the reason is that the ID class will be represented as String in runtime. So the $constructor_marker additional parameter is to guarantee the uniqueness of the MyEntity(String id, String title) constructor signature, cause this constructor could already have been defined. But I'm just speculating here.
Could you try to explicitly define this constructor in MyEntity class and see if it works?
Kotlin inline classes use name mangling.
So I believe your Room database cannot find the getter and setter for you ID field.
Try to add:
...
#get:JvmName("getID")
#set:JvmName("setID")
#PrimaryKey val id: ID,
before your ID parameter declaration to disable mangling.
It helps to me
With the answer from Lyubomyr Ivanitskiy and some tinkering it can be done.
#Entity
class Test(
#PrimaryKey(autoGenerate = true)
#get:JvmName("getId")
#set:JvmName("setId")
var id: ID,
) {
constructor(): this(ID(0)) // This is required as replacement for
constructor with actual fields
}
When trying to load this entity using a dao it will fail due to the getter method not being generated. It does not work for me using the inner class ID. So it needs to be tricked like this:
#Dao
interface TheDao {
#Deprecated(
"This is just for the generated Dao_Impl",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("getByIdRealId(theID)")
)
#Query("select * from test where id = :theID")
fun getByIdLongType(theID: Long): Test
}
fun TheDao.getByIdRealId(theID: ID): Test = getByIdLongType(theID.id)
This will not prevent using the getById with Long parameter but generate at least a warning about it.
TestCode:
#Test
fun createAndLoadTest() {
val toBeSaved = Test(ID(42))
dao.save(toBeSaved)
val fromDB = dao.getByIdRealId(ID(42))
fromDB shouldNotBe null
fromDB.id shouldNotBe 42
fromDB.id shouldBe ID(42)
}
Here's my Room Entity:
#Entity(tableName = "matched_users")
data class MatchedUser(
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "match_id") val matchId: String
)
and here is me instantiating it in my Fragment:
private fun pass(){
CoroutineScope(coroutineContext).launch {
val match = MatchedUser()
CustomApplication.database?.matchedUsersDao()?.addMatchUid(match)
Log.d(TAG, "Added matchId to DB")
}
return removeUser2()
}
When I hover over MatchedUser() it still says I need to pass a parameter for id .. but it is meant to be autogenerated as stated in the Entity.
Any idea why?
In kotlin data class each variable should be initialized, so you may set default arguments in your data class constructor, like this:
#Entity(tableName = "matched_users")
data class MatchedUser(
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "match_id") val matchId: String
){
constructor(matchId: String): this(Int.MIN_VALUE, matchId)
}
Now you can insert data by providing only match_id to the constructor of the data class, like this:
private fun pass(){
CoroutineScope(coroutineContext).launch {
val match = MatchedUser("1")
CustomApplication.database?.matchedUsersDao()?.addMatchUid(match)
Log.d(TAG, "Added matchId to DB")
}
return removeUser2()
}
You must add the constructor and getter method in the class MatchedUser.
Then create an instance with parametr id = 0:
val match = MatchedUser(0)
If you are using Kotlin class, you must assign default values to the primary constructor either through secondary constructor or directly creating its object.
Secondary constructor inside the MatchedUser class having default values of declared types:
constructor():this(0, "")
val match = MatchedUser()
OR
Having no additional constructor and simply initializing class object using primary constructor:
val match = MatchedUser(0,"")
Right code:
class MainActHandler(val weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code:
class MainActHandler(weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code screenshot
The only difference is the "val" has been deleted and cannot be resolve.
Which might be important is that it's a inner class.
BUT
This one class without "val/var" in constructor parameter is working:
class BookInfo(convrMgr: ConversationMgr, id: String, queue: RequestQueue, queueTag:String) {
val TAG = "BookInfo"
var title: String? = ""
init {
val url = "https://api.douban.com/v2/book/$id"
// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
Log.d(TAG + " Response", response.substring(0))
// Parse JSON from String value
val parser = Parser()
val jsonObj: JsonObject =
parser.parse(StringBuilder(response.substring(0))) as JsonObject
// Initial book title of book properties.
title = jsonObj.string("title")
Log.d(TAG + " Book title", title)
convrMgr.addNewMsg(title)
},
Response.ErrorListener { error -> Log.e(TAG + " Error", error.toString()) })
// Set the tag on the request.
stringRequest.tag = queueTag
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
And if I add var/val before "queue: RequestQueue", I'll get suggestion:
"Constructor parameter is never used as a property less. This inspection reports primary constructor parameters that can have 'val' or 'var' removed. Unnecessary usage of 'val' and 'var' in primary constructor consumes unnecessary memory."
I am just confused about it.
When you write val/var within the constructor, it declares a property inside the class. When you do not write it, it is simply a parameter passed to the primary constructor, where you can access the parameters within the init block or use it to initialize other properties. For example,
class User(val id: Long, email: String) {
val hasEmail = email.isNotBlank() //email can be accessed here
init {
//email can be accessed here
}
fun getEmail(){
//email can't be accessed here
}
}
Constructor parameter is never used as a property
This suggestion is saying that you do not use this property in place apart from the initialization. So, it suggests you to remove this property from the class.
Constructor parameters must use var or val when they are used as a property elsewhere in the class. They do not need to be properties if they are only used for class initialization.
In the example below, the parameter must be a property (var or val) because it is used in a method:
class A(val number: Int) {
fun foo() = number
}
In this other example, the parameter is only used to initialize the class, so it does not need to be a property:
class B(number: Int): A(number) {
init {
System.out.println("number: $number")
}
}
This might be a late answer but the magic lies under the hood:
Based on #BakaWaii's answer:
Putting var/val will make the variable a property of the class and not putting it will make it a parameter of only the constructor function.
So what does it mean, to understand lets look into some code:
class Test(a: Int){}
Now Lets see the decompiled java code:
public final class Test {
public Test(int a) {
}
}
So now if I try to access a using the object of Test() like the below code:
Test t = new Test(10);
t.a //Error
It will give me error. Unresolved reference: a. Why because a is a parameter of the constructor only.
Now if we put var/val in the paramater like below:
class Test(var a: Int){}
The decompliked Java code will become:
public final class Test {
private int a;
public final int getA() {
return this.a;
}
public final void setA(int var1) {
this.a = var1;
}
public Test(int a) {
this.a = a;
}
}
Thus it will not only give you a class property but also give you getter/setters for setting the values.
Now the next question arises if the field a is private how can it be accessed. Simple answer in Java you cannot, i.e. if you are calling the KT class from a Java you will not be able to assign value of a like Test(1).a = 10 but will have to use Test(1).setA(5).
But as kotlin internally handles getters/setters Test(1).a = 5 will be ok.
For #Parcelize to work you need to open up the super's properties and override them in the child:
abstract class Goal(open var number: Int, open var name: String) : Parcelable
#Parcelize
class OperationalGoal(override var number: Int, override var name: String, var description: String) : Goal(number, name)```
In very simple terms, use var or val in class constructor parameters when you want to use that variable, say, inside a method within that class. Thus you're effectively turning them into properties and not just mere constructor or method parameters.
class User(var name: String, age: Int) {
var str = "John"
var num = 18
fun setName(){
name = str // due to using var on our class constructor parameter, we can access the constructor variable *name* inside this setter method. *name* is a property parameter thanks to the var keyword.
}
fun setAge(){
age = num // this will result in a compiler error, because *age* is just a parameter, notice that var wasn't used in the *age* parameter within the class constructor, which means we can't access it like we did with *name*
}
}
Run this Kotlin Playground code to get a clearer idea of what's going on.