Kotlin: #Ignore'd properties persisted by Sugar ORM - android

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"

Related

Platform declaration clash kotlin

I am seeing the following error
Platform declaration clash: The following declarations have the same
JVM signature (getHosts()Landroidx/lifecycle/MutableLiveData;):
private final fun <get-hosts>(): MutableLiveData<List> defined
in com.example.xx.viewmodel.HostsViewModel public final fun
getHosts(): MutableLiveData<List> defined in
com.example.xx.viewmodel.HostsViewModel
What am I doing wrong?
class HostsViewModel : ViewModel() {
private val hostsService = HostsService()
private val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
loadHosts()
}
}
fun getHosts(): MutableLiveData<List<Host>> {
return hosts
}
private fun loadHosts(){
hosts.value = hostsService.getHosts().body()
}
}
For every class property (val), Kotlin generates a getter called getHosts() and for var also a setter called setHosts(MutableLiveData<List<Host>> value) as per Java's convention. It hides it from the Kotlin user as getters and setters are usually just boilerplate code without offering much value. As such, your own getHosts() method clashes with the generated method at compilation. You have multiple possibilities to solve this issue:
Rename private val hosts to something else, e.g. private val internalHosts
Annotate the getHosts method with #JvmName("getHosts2"). If you do that though, consider the possibility that someone might call your code from Java and in that case, the caller would need to call getHosts2() in Java code, which might not be such nice API-design.
Reconsider your api design. In your case, you could simply make val hosts public and remove your getHosts() entirely, as the compiler will auto-generate getHosts() for you.
In addition to that, you might want to consider not exposing MutableLiveData in general as mentioned in the comments.
Edit:
Also, I would recommend that you do this:
val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
it.value = hostsService.getHosts().body()
}
}
and remove loadHosts to make your code more concise.

Kotlin : implenting an immutable class through the data class method but making sure the input values are clean

I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}

For some reason cant deserialize object from Firebase response

Everything looks good. Need help in finding a mistake in code.
By the logs that is the snapshot from firebase
DataSnapshot
{ key = SecondGame,
value = {background=https://firebasestorage.googleapis.com/v0/b/fantasygameapp11.appspot.com/o/background_black.jpg?alt=media&token=b3ec1477-6b52-48b4-9296-f57f63f26837, description=SecondGameDescription, tag=https://firebasestorage.googleapis.com/v0/b/fantasygameapp11.appspot.com/o/hot_icon.png?alt=media&token=65516b45-1aca-4cac-9a39-3eddefffe499,
title=SecondGame, type=regular} }
That is the model
data class GameUnit (val background: String, val description: String, val tag: String, val title: String, val type: String)
Thats the code of the response
mReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
GameUnit post = dataSnapshot.getValue(GameUnit.class);
}
I know it might be already asked but I need to find the issue first of all. Is it also possible the problem is that model is in Kotlin but firebase response in Java?
Error
com.google.firebase.database.DatabaseException: Class com.model.GameUnit does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped.
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-database##17.0.0:552)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(com.google.firebase:firebase-database##17.0.0:545)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertBean(com.google.firebase:firebase-database##17.0.0:415)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-database##17.0.0:214)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertToCustomClass(com.google.firebase:firebase-database##17.0.0:79)
at com.google.firebase.database.DataSnapshot.getValue(com.google.firebase:firebase-database##17.0.0:212)
at com.adultgaming.fantasygameapp.utils.FirebaseManager$1.onDataChange(FirebaseManager.java:47)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(com.google.firebase:firebase-database##17.0.0:75)
at com.google.firebase.database.core.view.DataEvent.fire(com.google.firebase:firebase-database##17.0.0:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(com.google.firebase:firebase-database##17.0.0:55)
The deserializer that comes with the Realtime Database (and also Cloud Firestore) Android SDKs requires that the class you pass to it look like a JavaBean type object. This means that it must have a default no-arg constuructor, as you can tell from the error message, and also setter methods that map to each database field.
Kotlin data classes don't provide a default no-arg constructor in order to ensure that all of its fields have an initial value. You can tell Kotlin that it's OK for all of the fields not to have an initial value by giving null or some other value as a default value:
data class GameUnit (
var background: String = null,
var description: String = null,
var tag: String = null,
var title: String = null,
var type: String = null
)
For the above data class, Kotlin will generate a default no-arg constructor for the Firebase SDK to use. It will also generate setter methods for each var. Note that each property is var and provides a default null value.
If this is not what you want your data class to look like, you won't be able to use automatic deserialization. You will have to read each value out of the snapshot, make sure they are each not null, and pass them all to the constructor that Kotlin provides.

Kotlin explain me about Backing Fields

I have been looking at Kotlin official tutorial. I came across the topic called Backing Fields
It says,
Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors. For these purposes, Kotlin provides an automatic backing field which can be accessed using the field identifier:
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
I got the above from this official link
My question is, is the "field" pointing to counter variable ?
Can someone please provide me an example for the backing field or describe me in an understanding word ?
Consider this class
class SomeClass {
var counter: Int = 0
set(value) {
if (value >= 0) field = value
}
}
In Android Studio go to Main menu -> Tools -> Kotlin -> Show Kotlin Bytecode and click Decompile in the Kotlin bytecode panel.
What you see is the equivalent code in Java.
public final class SomeClass {
private int counter;
public final int getCounter() {
return this.counter;
}
public final void setCounter(int value) {
if(value >= 0) {
this.counter = value;
}
}
}
The field keyword allows you to assign a value inside a custom setter. In kotlin counter = 3 will call set(3). So if you would define
var counter=0
set(value){
counter = value
}
It would recursively call itself until your stack is full and your process crashes.
The field keyword assigns the value directly without calling the setter again.
A Backing Field is just a field that will be generated for a property
in a class only if it uses the default implementation of at least one
of the accessors
Backing field is generated only if a property uses the default implementation of getter/setter. If you see the following code with perspective of Java. It looks correct. However in "kotlin" it’ll throw Exception.
class User{
var firstName : String //backing field generated
get() = firstName
set(value) {
firstName = value
}
var lastName : String //backing field generated
get() = lastName
set(value) {
lastName = value
}
val name : String //no backing field generated
get() = "{$firstName $lastName}"
var address : String = "XYZ" //^because there is no default //^implementation of an accessor
}
In Kotlin the above code snippet will throw StackOverflow because when we access or set property "first-name" or "last name" the default accessor will be called. So in Kotlin "user.firstName = "value"” is same as Java’s "user.setFirstName("value")".
So when "set(value) {firstName = "value"} " is called, then a recursive callhappens and compiler throws a Exception exception because we are calling setter inside the setter.
Solution to this problem is to user backing fields. In Kotlin a backing field can be accessed using "field" keyword inside accessors. Take a look at corrected code snippet below.
class User{
var firstName : String get() = field
set(value) {
field = value
}
var lastName : String get() = field
set(value) {
field = value}
}
}
How it works , let's understand by an example , consider this
class Person {
var name: String = ""
}
If nothing is specified, the property(name) uses the default getter and setter. It can, of course,
be modified to run whatever custom behaviour you need, without having to change
the existing code:
So if want set custom behaviour to name property than we modify above class to this
class Person {
var name: String = ""
get() = field.toUpperCase()
set(value) {
field = "Name: $value"
}
}
If the property needs access to its own value in a custom getter or setter (as in this
case), it requires the creation of a backing field. It can be accessed by using field, a
reserved word, and will be automatically created by the compiler when it finds that
it’s being used.

Room Database error with Kotlin Data Class

I've been moving into using Room, and I've run into a blocking issue. I've gone through and fixed all of the compile-time checks from the Room library, but am now encountering 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).
This appears twice at compile time with no evidence of which class this comes from, but I was able to figure out (by removing classes from the Database) that this was one of the files. I'm assuming it has something to do with the Primary Key being a string instead of an Int (this is one of two classes that uses this), but nothing in the documentation indicates what the issue would be, and in fact the documentation shows that strings are valid Primary Keys.
#Entity(tableName = "inspections")
data class Inspection(
#SerializedName("id")
var id: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
I've tried a few things to try and get around this.
Remove the data attribute of this class to make it a normal POKO
Remove the variables from the default constructor, and place them into the class
Remove the Ignore from the empty constructor (note, this causes a different issue, Room cannot pick a constructor since multiple constructors are suitable - the Ignore annotation on a default constructor gets around this.) This is the part which perplexes me the most - removing this says "multiple constructors are valid", keeping it says "no constructors are valid".
Updated: Adding a few more relevant code snippets from my project.
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
.....
implementation 'android.arch.persistence.room:runtime:1.0.0-alpha9-1'
implementation 'android.arch.persistence.room:rxjava2:1.0.0-alpha9-1'
kapt 'android.arch.persistence.room:compiler:1.0.0-alpha9-1'
Database class
#Database(entities =
arrayOf(Account::class, Category::class,
Inspection::class, InspectionForm::class,
InspectionFormItem::class, InspectionFormsStructure::class,
InspectionItemPhoto::class,
InspectionItem::class, LineItem::class,
LocalPhoto::class, Rating::class,
Structure::class, SupervisoryZone::class,
Upload::class, User::class),
version = 16)
#TypeConverters(Converters::class)
abstract class OrangeDatabase : RoomDatabase() {
abstract fun inspectionDao(): InspectionDao
abstract fun localDao(): LocalDao
abstract fun ratingsDao(): RatingsDao
abstract fun structureZoneDao(): StructureZoneDao
abstract fun userAccountDao(): UserAccountDao
}
Converters
class Converters {
#TypeConverter
fun fromTimestamp(value: Long?): Date? {
return if (value == null) Date() else Date(value)
}
#TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time ?: 0
}
#TypeConverter
fun fromStringToArray(value: String?): Array<String>? {
return value?.split(",")?.toTypedArray() ?: arrayOf()
}
#TypeConverter
fun stringToStringArray(strings: Array<String>?): String? {
return strings?.joinToString(",") ?: ""
}
}
Another data class
#Entity(tableName = "users")
data class User(
#PrimaryKey
#SerializedName("id")
var id: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
UserPermissions class:
data class UserPermissions(
#SerializedName("id")
var pid: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
The problem in your case is, that if you have nullable values Kotlin will generate several constructors for each possible constructor.
That means that you have to define a default constructor and fill it with default values.
If you want to have another one which should be ignored you should make sure to use the parent constructor with all those parameters.
Example:
#Entity(tableName = "inspections")
data class Inspection(
#SerializedName("id")
var id: Int = 0,
#PrimaryKey
#SerializedName("guid")
var guid: String = "",
#SerializedName("score")
var score: Double = 0.0,
#SerializedName("notification_sent_at")
var notificationSentAt: Date = Date(),
var wasUploaded: Boolean = false) {
#Ignore
constructor() : this(0, "", 0.0, Date(), false)
}
In this case only two constructors will be generated "under the hood". If you have nullable values you will have all possible constructors available.
Example:
data class Test(var id: Int = 0, var testString: String? = null, var testBool : Boolean? = null) {
constructor(0)
}
generates
constructor(var id:Int)
constructor() : this(0)
constructor(var id:Int, var testString: String)
constructor(var id:Int, var testBool: Boolean)
constructor(var id:Int, var testString: String, var testBool : Boolean)
// .. and so on
Since you'r looking for an official documentation, you may want to look at Overloads Generation.
After testing your class which works flawlessly i found in another post that you have to check if you used apply plugin: 'kotlin-kapt' in your Gradle.
Double check that you've valid type converters for your Date class. I wrote that issue longer time ago.
After recoding your stuff above it worked just fine by adding a UserPermissions class like that:
data class UserPermissions(var permissionid: String)
Edit: After using your UserPermission class everything worked just fine. Please take care if you use the proper import (util.Date instead of sql.Date for example).
Another problem is that your using an old very buggy library of room.
The current version (while writing this) is
implementation "android.arch.persistence.room:runtime:1.0.0-beta2"
kapt "android.arch.persistence.room:compiler:1.0.0-beta2"
implementation "android.arch.persistence.room:rxjava2:1.0.0-beta2"
I wrote an issue long time ago
The issue was extremely difficult to debug and harder to reproduce, but I found the issue. I was using an #Embedded object, but the result that was going in was actually a List of that object. This was giving trouble to the automatic Embed task, and there wasn't a perfect Converter that could be written for it.
#SerializedName("range_choices")
#Embedded
var rangeChoices: List<RangeChoice>? = null,
I had to annotate that with #Ignore and instead, I'll be saving the results of this list to its own table, now the new table range_choices.
Your Primary keys should be like given below using Annotation Use-site Targets
#field:PrimaryKey #field:SerializedName("guid") var guid: String = ""
and
#field:PrimaryKey #field:SerializedName("id") var id: Int = 0
Try to avoid using nullable values and make everything have some kind of default value. That's the simpliest way to solve this issue.
If you really want to use them, then you may create a constructor, containing all of them.

Categories

Resources