Android/Room/ViewModel : Error while saving ViewModel to Room Database - android

I have an activity which uses a ViewModel class to store and manage UI-related data. The view model class used in the activity has a structure similar to the one given below:
class SomeViewModel:ViewModel(){
#PrimaryKey(autoGenerate=true)
var id=0
var field1:Array<Double?>=arrayOf(null,null)
var field2:Array<Double?>=arrayOf(null,null)
var field3:Array<Double?>=arrayOf(null,null)
#Ignore
val someFragments=HashMap<String,Fragment>()
#Ignore
val someMap=HashMap<String,Int>()
}
What I am trying to do is to save the data in the view model object to the local database when back is pressed to close the activity. But the issue is when I am trying to do so I am getting an error.
Cannot figure out how to save this field into database. You can consider adding a type converter for it. - mBagOfTags in androidx.lifecycle.ViewModelerror: Cannot find getter for field. - mBagOfTags in androidx.lifecycle.ViewModelerror: Cannot find getter for field. - mCleared in androidx.lifecycle
I am using a TypeConverter identical to the one given below :
class Converter{
#TypeConverter
fun fromArrayDouble(field:Array<Double?>):String{
val s=StringBuilder("")
var first=true
for(k in field){
s.append(k.toString())
if(first){
s.append(",")
first=false
}
}
return s.toString()
}
#TypeConverter
fun fromString(str:String):Array<Double?>{
val parts=str.split(',')
val res=ArrayList<Double?>()
for(p in parts){
try{
res.add(p.toDouble())
}catch(e:Exception){
res.add(null)
}
}
return res.toTypedArray()
}
}
and a database class similar to :
#Database(entities=[SomeViewModel::class], version=1, exportSchema=false)
#TypeConverters(Converter:class)
abstract class SomeDatabase:RoomDatabase(){
// database definition
}
I don't understand where I am going wrong. Any help will be highly appreciated.

Please refer to this answer from another post.
Kotlin room does not allow defining variables in entity class start of 'i' character. I had the same error. I solved it, albeit difficult. Replace id with pId and it will be fine.
Or probably you need to add getter to be found by Room.
fun getId(): Int {
return id
}

Related

Convert Firestore document to list of objects

I have this Firestore document Quiz_android that looks list this:
It is a simple array with maps in it. Now I would like to bind those results to some objects in Kotlin. Therefore I have made the following:
data class QuizBody(
val questions: List<Question>
)
data class Question(
val question: String,
val answers: List<String>,
val answer: Int
)
A Quizbody is just all the questions for the quiz in a list, and in that list, I have classes of Question which should be able to store all the data from the call.
But how do I bind the result from the call to those objects?
suspend fun getQuestions(quizToGet: String) {
try {
//firestore has support for coroutines via the extra dependency we've added :)
withTimeout(5_000) {
firestore.collection("Quizzes").document(quizToGet).get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val result = task.result
if (result.exists()) {
val myObject = result.toObject(QuizBody::class.java)
println(myObject)
}
}
}
}
} catch (e: Exception) {
throw QuizRetrievalError("Retrieving a specific quiz was unsuccessful")
}
}
I have made this but this does not work.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hva.madlevel7task2, PID: 3995
java.lang.RuntimeException: Could not deserialize object. Class com.hva.madlevel7task2.model.QuizBody does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped
Edit:
I have updated the data class:
data class QuizBody(
var questions: List<Question>? = null
)
data class Question(
var question: String? = null,
var answers: List<String>? = null,
var answer: Int? = null
)
suspend fun getQuestions(quizToGet: String) it still the same, now I get this in the console:
I/QuizViewModel: function: getListQuestions
W/Firestore: (24.1.1) [CustomClassMapper]: No setter/field for Questions found on class com.hva.madlevel7task2.model.QuizBody (fields/setters are case sensitive!)
I/System.out: QuizBody(questions=null)
The following error:
java.lang.RuntimeException: Could not deserialize object. Class com.hva.madlevel7task2.model.QuizBody does not define a no-argument constructor.
Is very straightforward in my opinion. Your class "QuizBody" does not have a no-argument constructor. When you try to deserialize an object that comes from a Firestore database, the Android SDKs require that the class should mandatorily have a default no-argument constructor.
In Kotlin, the data classes don't provide a default no-arg constructor if all the properties of the class are declared with val. For such properties, Kotlin requires that their values be specified in the constructor since they can't possibly change later. So this is required because we need to ensure the compiler that all the properties have an initial value. You can provide to all of the properties an initial value of null or any other value you find more appropriate. So your classes should look like this:
data class QuizBody(
var questions: List<Question>? = null
👆 👆
)
data class Question(
var question: String? = null,
var answers: List<String>? = null,
var answer: Int? = null
)
Now adding the properties in the constructor, Kotlin will automatically generate a default no-argument constructor. In this way, the Firebase Android SDK will be able to use to generate setters for each property. If don't make this change, you won't be able to use automatic deserialization. You'll have to read the value for each property out of the DocumentSnapshot object and pass them all to Kotlin's constructor.
Edit:
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
val myObject = document.toObject(QuizBody::class.java)
println(myObject)
}
}

Kotlin/Room/Android Error: Getting error while Querying for nested object which can have null fields

I am trying to request data from an API which responds with an array of nested JSON object in which some entries of the object can be null.
Each of the object has a structure similar to the following :
data class A(
#ColumnInfo("item1InA")
val item1InA:Double?=null,
#ColumnInfo("item2InA")
val item2InA:Double?=null,
)
data class B(
#ColumnInfo("item1InB")
val item1InB:Double?=null,
#ColumnInfo("item2InB")
val item2InB:Double?=null,
)
#Entity(tableName="temp_table")
data class Combination(
#PrimaryKey
#NonNull
#ColumnInfo("key")
val key:String='-',
#ColumnInfo("originalItem1")
val originalItem1:String='-',
#ColumnInfo("originalItem2")
val originalItem2:String='-',
#Embedded
val a:A=A(),
#Embedded
val b:B=B(),
)
I am querying the database in the application to get the list of the all Combination objects using the following interface :
#Dao
interface CombinationDao{
#Query("SELECT * FROM temp_table")
suspend fun getAll():List<Combination>
}
The issue is that i am getting the error: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter
When I checked on the directed links to the CombinationDao_Impl.java file, I got the following error: both methods have same erasure, yet neither overrides the other
I don't understand what I am doing wrong here. Can anyone help me get on the right track?
Sample JSON
{
"A":{
"item1InA":23.0,
"item2InA":null,
},
"B":{
"item1InB":67327.309,
"item2InB":null,
},
"key":"thisIsUnique",
"originalItem1":"hello",
"originalItem2":"world",
}
According to the documentation at Embedded | Android Developers,
When an embedded field is read, if all fields of the embedded field (and its sub fields) are null in the Cursor, it is set to null. Otherwise, it is constructed
The issue with my code was that for some JSON objects each of the fields of the embedded objects were null causing room to simply ignore to construct the object making the variables a and b to point at null.
Changing the code of the entity class to,
data class Combination(
#PrimaryKey
#NonNull
#ColumnInfo("key")
val key:String='-',
#ColumnInfo("originalItem1")
val originalItem1:String='-',
#ColumnInfo("originalItem2")
val originalItem2:String='-',
#Embedded
val a:A?=A(),
#Embedded
val b:B?=B(),
)
solved the issue for me.

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)
}

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.

Kotlin data class of RealmObject

I'm using Kotlin and Realm to write a data class
data class AuthToken(val register: Boolean,
val token: String,
val tokenSecret: String,
val user: AuthUser)
I have to save the data to db, so I use Realm to save it. But as we know, if I want to save the class to Realm, the AuthToken class has to extend RealmObject.
That's the problem, Kotlin says data classes can't extend classes.
so I give up data class, just using a normal Kotlin class as a model then another question comes:
Kotlin class has no getter or setter. As we know Realm class have to set all the property private and write getter and setter.
Now i'm wondering how to solve the problem.
Realm doesn't support Data classes currently. You can see an example of how to write Realm compatible model classes in Kotlin here: https://github.com/realm/realm-java/tree/master/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model
public open class Person(
#PrimaryKey public open var name: String = "",
public open var age: Int = 0,
public open var dog: Dog? = null,
public open var cats: RealmList<Cat> = RealmList(),
#Ignore public open var tempReference: Int = 0,
public open var id: Long = 0
) : RealmObject() {
Any Kotlin property in any class has a getter and a setter. So I believe you code should just work as yourself suggested (without data modifier).
https://kotlinlang.org/docs/reference/data-classes.html#data-classes
https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties
P.S. I agree that the docs on properties are unclear on the subject

Categories

Resources