Convert Firestore document to list of objects - android

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

Related

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

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
}

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.

Could not deserialize object. Class does not define a no-argument constructor.If you are using ProGuard, make sure these constructors are not stripped

https://github.com/neuberfran/JThings/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFran.kt
I have this POJO above with the error mentioned in the topic. I know it is a mistake already mentioned here, but I have tried several classes (besides this one) and I have not been successful, since my model/POJO class (and Code implementation) is different from several that I saw:(Every help is welcome)
Could not deserialize object. Class does not define a no-argument
constructor. If you are using ProGuard, make sure these constructors
are not stripped (found in field 'value')
Change made to the garagem document, exchanged value for valorb, etc...
The error is very clear, your class "FireFran" doesn't have a no-argument constructor. When you try to deserialize an object from Cloud Firestore, the Android SDKs require that the class must have a default no-arg constructor and also setters that map to each database property.
In Kotlin, the data classes don't provide a default no-arg constructor. So you need somehow 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 "FireFran" might look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var value: FireFranValue? = null //Newly added
) {
//var value: FireFranValue = FireFranValue(false, 0)
companion object Factory {
fun create() :FireViewModel = FireViewModel()
var COLLECTION = "device-configs"
var DOCUMENT = "alarme"
var FIELD_userId = "userId"
}
}
Now adding the properties in the constructor, Kotlin will automatically generate a default no-arg constructor. In this way, the Firebase Android SDK will be able to use. It will also generate setters for each property. Please see that each property is var and not a val, and provides a default null value in case of "id" and "userId".
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:
In your screenshot, the "value" property is on an object of type "FireFranValue", which has only two properties, "brightness" and "on". To be able to read the data under "value", your "FireFran" class should contain a new property of type "FireFranValue". Please check above the class.
If you don't want to use automatic deserialization, you can get the value of each property individually. For example, you can get the value of the "userId" property using DocumentSnapshot's getString(String field) method:
val userId = snapshot.getString("userId")
Edit2:
Your three classes should look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue = FireFranValue(false),
var valorb: FireFranValueB = FireFranValueB(openPercent = 0)
)
data class FireFranValue(
var on: Boolean // = false
)
data class FireFranValueB(
var openPercent: Number // = 0
)
Edit3:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue? = null,
var valorb: FireFranValueB? = null
)
data class FireFranValue(
var on: Boolean? = null
)
data class FireFranValueB(
var openPercent: Number? = null
)
Now, "on" and "openPercent" will get the values from the database.
Please check the following class declaration:
You can define a no-arg constructor in kotlin like this:
data class SampleClass(
val field1: String,
val field2: String
) {
constructor(): this("", "")
}
First thing: It was not necessary to create the field (map type) called valorb. It was resolved with value.openPercent
As I have two documents (alarme and garagem) I created two POJO classes (FireFran and FireFranB)
https://github.com/neuberfran/JThingsFinal/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFranB.kt
The secret was to treat the value.on and value.openPercent fields as maps (as facts are):

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.

Firestore - how to exclude fields of data class objects in Kotlin

Firestore here explains, how I can use simple classes to directly use them with firestore: https://firebase.google.com/docs/firestore/manage-data/add-data
How can I mark a field as excluded?
data class Parent(var name: String? = null) {
// don't save this field directly
var questions: ArrayList<String> = ArrayList()
}
I realize this is super late, but I just stumbled upon this and thought I could provide an alternative syntax, hoping someone will find it helpful.
data class Parent(var name: String? = null) {
#get:Exclude
var questions: ArrayList<Child> = ArrayList()
}
One benefit to this is that, in my opinion, it reads a little clearer, but the main benefit is that it would allow excluding properties defined in the data class constructor as well:
data class Parent(
var name: String? = null,
#get:Exclude
var questions: ArrayList<Child> = ArrayList()
)
Since Kotlin creates implicit getters and setters for fields, you need to annotate the setter with #Exclude to tell Firestore not to use them. Kotlin's syntax for this is as follows:
data class Parent(var name: String? = null) {
// questions will not be serialized in either direction.
var questions: ArrayList<Child> = ArrayList()
#Exclude get
}

Categories

Resources