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.
Related
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)
}
}
I am using a POJO data class with GSON to parse the data which is being called from the Firestore database.
For example, I have a POJO class with few non-nullable, and nullable values like userID
data class Users(id:String="", userID:String="" ...)
I am then using GSON to parse the JSON data to object for that class
val gson = Gson()
val jsonObjects = gson.toJson(querySnapshot.data)
val parseData = gson.fromJson(jsonObjects,Users::class.java)
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
I am using a check like if(userID == ""){return false} . But as the number of non-nullable fields grows it gets tedious and there must be a better way to check this.
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
If you don't want to have null values at all, why would you then let the user the possibility to provide incomplete data? The simplest solution would be to restrict the data that is added to the database. How? Simply by creating some constraints. For example, your users cannot get access to a feature in your app if they do not fill in all the required fields. That's an operation that is widely used.
If you already have null values, then checking against nullity is a solution you can go ahead with. In Kotlin, null safety can be handled in many ways, either by checking for null in conditions, using safe calls, elvis operator or even using the !! operator.
Maybe the extension method of Kotlin is an accepted approach.
Let me show a demo, I assume the class User looks like this:
data class User(
val id: String,
val userId: String? // nullable
)
Create a extension method User.isValid() to verify the fields you want.
fun User.isValid() = when {
userId.isNullOrEmpty() -> false
else -> true
}
The method isNullOrEmpty() is in stdlib of Kotlin. I test the code, it works fine.
val user1 = User("id 001", null)
val user2 = User("id 002", "userId 001")
println(user1.isValid()) //==> print false
println(user2.isValid()) //==> print true
Now, back to your worry point:
...But as the number of non nullable fields grow it gets tedious
I changed the class User
data class User(
val id: String,
val userId: String?,
val email: String?,
val name: String?
)
it means that when the fields of userId, email, name, any of them is null, the User is invalid.
Just add conditions in extention method, like this:
fun User.isValid() = when {
userId.isNullOrEmpty()
|| email.isNullOrEmpty()
|| name.isNullOrEmpty() -> false
else -> true
}
We just need to maintain the method of isValid.
conclusion
Kotlin Extension Method can be used in your case.
It's better don't use id="", also can create an extension method for this empty string if need.
fun String.Companion.empty() = ""
data class User(
val id: String,
val userId: String? = String.empty()
...
)
All the extension methods can be placed in a class, like UserExt.kt for easy maintaining.
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)
}
I have this POJO:
data class User(
var uid: String? = null,
#get:Exclude
var name: String? = null
) : Serializable
If I construct an object like this:
docRef.set(User("longUid", null))
I get in the database an object with a single property. If I use:
docRef.set(User("longUid", "John"))
I get the same behavior. Is there any possibility to exclude the name property only if it's null? If I pass a real value I want the object to contain both values.
Maybe you can remove #Exclude from your User POJO. What do you need it for other than what you are asking? When you have an User with no name property, you can simply create an User object avoiding name property:
val userA = User("longUid")
When you set a document with userA as shown above, you won't have a name field for it in Firestore. And when you get userA from Firestore applying toObject(Class<T>) function to your DocumentSnapshot it should return an object with no name, so your new User object's name would be null.
I am working on android application and want to manage null value that is comes from API.
I did not found the best solution.
I also try these methods
1. While generating getter in model class i give a conditional statement in getter like
if(value == null){
value = ""
}
but this is not a right way because of i have to write this code for each and every getter in whole application.
2. replace the string from 'null' to "" before JSON parsing.
this is replace all type of value (int/float etc) in to blank string that is invalid.
You can use JsonDeserializer to create your Gson.
You can create model class like this
class ExampleModel {
#SerializedName("id")
#Expose
val id: Int? = null
#SerializedName("category")
#Expose
val category: String? = null
}
This class will be able to handle null value and data as well.