I'm using lombok to generate constructors, getters and setters for my models. When i try to use lombok to generate the constructor for my entity class, I get this error
Error:(14, 8) 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:
Region(int,java.lang.String,java.lang.String) -> [param:arg0 -> matched
field:unmatched, param:arg1 -> matched field:unmatched, param:arg2 ->
matched field:unmatched]
but writing the constructor manually works. Can anyone help me figure out what's wrong?
My entity class is shown below
#Value
#Entity
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}
Room version: 1.1.0
Lombok version: 1.16.20
The matching seems to fail because the constructor parameter names are not available at runtime.
Since version 1.16.20 lombok does not generate #ConstructorProperties annotations any more (which would carry those names).
Try adding lombok.anyConstructor.addConstructorProperties = true to your lombok.config, and lombok will generate a #ConstructorProperties annotation for your constructor. (See https://projectlombok.org/features/configuration for details on how to configure lombok.)
EDIT: The problem is the annotation processing during compilation. Both Room and lombok hook into javac as annotation processors, and they do not work nicely in combination. So at the moment, the only stable solution is to delombok first.
You can use the following setup:
#Entity
#Getter
#Setter
#AllArgsConstructor(onConstructor = #__({#Ignore}))
#NoArgsConstructor
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}
This will make Room use the default constructor and set the value via the provided setters. Additionally you have a constructor that accepts all arguments for object instantiation, but will be ignored by Room.
Note: Object won't be immutable that way
Please try this as below with #Data annotation.
#Value
#Entity
#Data
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}
Related
This project has MVVM, Room, Koin and Coroutines.
Project code:
#Dao
interface MovieDao {
#get:Query("select poster_path from Movie")
val getImgPopularMovieList: LiveData<List<String>>
}
What means "#get:Query" instead "#Query" and "val" instead "fun" in DAO interface?
And how to add WHERE clause without a function to pass parameter or using a constant? (Using mandatorily val instead fun). Example: "select poster_path from Movie WHERE category = :someParameterOrConstant"
I would like to leave this answer, in case someone new in Kotlin like me encounters this.
I was learning codelab about Android, and #get:Query mentioned in the project, which lead me to this question, then after a good research I found this concept relates to Kotlin than Android and it is called Use-site Targets annotation.
Annotation Use-site Targets
Simply put, annotation use-site targets allow any #Annotations in your source code to end up at a very specific place in your compiled bytecode or in the Java code generated by kapt.
Kotlin supports the following values of the use-site targets that correspond to:
delegate – a field storing a delegated property
field – a field generated for a property
file – a class containing top-level functions and properties defined in that file
get/set – the property getter/setter
param – a constructor parameter
property – the Kotlin's property, it is not accessible from Java code
receiver – the receiver parameter of an extension function or property
Let's use simple class:
class Example(#param:ColorRes val resId:Int )
We used the use-site of param with #ColorRes, that will apply #ColorRes to the constructor parameter in the generated Java class:
public final class Example {
private final int resId;
public final int getResId() {
return this.resId;
}
public Example(#ColorRes int resId) {
this.resId = resId;
}
}
Let's change the use-site to field:
class Example(#field:ColorRes val resId:Int )
Now #ColorRes annotation will be applied to the resId field of the generated class:
public final class Example {
#ColorRes
private final int resId;
public final int getResId() {
return this.resId;
}
public ViewModel(int resId) {
this.resId = resId;
}
}
By using the use-site of get:
class Example(#get:ColorRes val resId:Int )
The getter method of resId field will have the #ColorRes annotation:
public final class Example {
private final int resId;
#ColorRes
public final int getResId() {
return this.resId;
}
public Example(int resId) {
this.resId = resId;
}
}
So!
In our Android code:
#Dao
interface MovieDao {
#get:Query("select poster_path from Movie")
val popularMovieImageList: LiveData<List<String>>
}
The annotation use-site of get will require th implementation of MovieDao to apply Query("...") to getter method of popularMovieImageList:
public final class MovieDaoImpl {
private final LiveData<List<String>> popularMovieImageList;
#Query("select poster_path from Movie")
public final LiveData<List<String>> getPopularMovieImageList() {
...
}
}
NOTE: Previous java code is from my imagination, I have no idea how generated implementation for this Dao by Room will look, just to support my explanation.
So far!
Why #get:Query instead of #Query?
Well, from the docs of Android we use #Query for methods:
Marks a method in a Dao annotated class as a query method.
And from the docs of Kotlin we don't use the Annotation Use-site targets for methods but for a property or a primary constructor parameter:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode.
As far as I learned in Kotlin, property getter cannot have parameter but instead use methods for that.
I think all other confusions should be clear now.
References:
Use-site annotation targets - Mastering Kotlin by Nate Ebel
Kotlin Annotations - Baeldung
Advanced Kotlin - Part 2: Use-Site Targets - American Express
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)
}
I'm getting a compilation build error when I add a MutableLiveData object to my view model in my Android Studio project. I'm not calling getUser() or setUser() anywhere yet and I added the exact same object to a different view model in my project and haven't gotten an error, so I'm not sure what the problem is.
Error:
error: Parceler: Unable to find read/write generator for type androidx.lifecycle.MutableLiveData<com.example.demometvtest1.User> for com.example.demometvtest1.RegisterViewModel.user
RegisterViewModel.java:
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
#Parcel
public class RegisterViewModel extends ViewModel {
public MutableLiveData<User> user = new MutableLiveData<>();
public void setUser(String user) {
return user;
}
public MutableLiveData<User> getUser() {
this.user.setValue(user);
}
}
The problem is the annotation #Parcel: you are trying to automatic generate writeToParcel() & createFromParcel() and the annotation processor doesn't find a read/write implementation for MutabileLiveData (that it's not parcelable).
Remove the annotation, make the class implement the parcelable interface and make your own implementation of parcelable metods writeToParcel() & createFromParcel() if you need It or simply remove the annotation.
In my Android app, I'm using Kotlin in conjunction with SugarORM and I have encountered an issue trying to prevent some properties from being persisted. Ironically, the #com.orm.dsl.Ignore annotation seems to be ignored when used in Kotlin classes.
As an example,
1) let's declare two seemingly identical models:
// JavaUser.java
public class JavaUser extends SugarRecord {
public String login = "login";
#Ignore public String password = "password";
}
// KotlinUser.kt
class KotlinUser : SugarRecord() {
var login: String = "login"
#Ignore var password: String = "password"
}
2) persist their instances
JavaUser().save()
KotlinUser().save()
3) and take a look at what's actually being persisted:
sqlite> select * from java_user;
ID|LOGIN
1|login
sqlite> select * from kotlin_user;
ID|LOGIN|PASSWORD
1|login|password
I realize that it may have something to do with Kotlin annotation processing but I'm just not sure how I can go about it. Any suggestions are most welcome.
The core difference between your Java and Kotlin code is that in Java you use fields, but in Kotlin you use properties. See the Properties and Fields section in documentation.
You may try the following solutions and see what works best with SugarORM:
1. Make Kotlin expose fields:
#Ignore #JvmField var password: String = "password"
2. Apply your annotation to the private backing field:
#field:Ignore var password: String = "password"
I am using Lombok for one of my apps.
I have a class declared with the annotation #Builder. The fields are annotated with #SerializedName("xxxxx") in order to support Gson.
However, one of the fields is a List so I would like to use the #Singular annotation for them, but looks like the lib doesn't know about this annotation.
#Builder
public class ProductForm {
#SerializedName("title") private String title;
#SerializedName("description") private String description;
#SerializedName("images") private List<ImageForm> imageFormList;
#SerializedName("active") private boolean active;
}
Does anyone know why?
Doc here
Using lombok 1.16.4 and your code (used #lombok.Builder not the deprecated one) I've no compile error when adding #lombok.Singular to imageFormList in your code.
So you probably forgot to import #lombok.Singular or used an old version of lombok.