Default values from Retrofit Server response are being shown as null - android

I am calling an api which will gives me some values and some not. So i have given some variables default values. But even when i am not getting them from server , they are being shown as null in log and app is crashing.
Here is the model class:
data class FeedbackData(
var questionNumber: Int = 0,
var imageUri: Uri? = null,
#SerializedName("id") val id: Int,
#SerializedName("question") val question: String? = null,
#SerializedName("answer") var answer: Answer = Answer()
)
data class Answer(
#SerializedName("description") var description: String = "",
#SerializedName("image") var image: String = ""
)
if you see i have given answer a default object (default initialization) incase server doesnt send it. My requirement is that if server sends it i should have it,if not then i should be able to write that answer object. But i am getting null pointer exception when i am trying to access that answer object.
Here is the log
E/xoxo: received list: [FeedbackData(questionNumber=0, imageUri=null, id=1, question=How was the event?, answer=null), FeedbackData(questionNumber=0, imageUri=null, id=2, question=Did you face any issue?, answer=null)
And here is the server response:
{"id":1,"question":"How was the event?"},
{"id":2,"question":"Did you face any issue?"}

So for now i have given value to all the fields above, to id also which did not have default value and now it seems to work. It is seeming like a cheat solution but its working.

Based on the code you have shown, you are initializing
var answer: Answer = Answer()
But since server is returning null, it is replacing the default Answer with null value.
One possible solution could be to provide getter method, something on these lines
var name: answer : Answer
get() = field?:Answer()
Note: Verify syntax.

If you are using progaurd, keep your model classes away from obfuscation.
Use keepclassmembers in progaurd.

Related

Verify if POJO data class was mapped correctly

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.

Correct way to parse eterogeneous arrays of data

I have this model that I would like to parse from JSON:
class CFInsertedValuesStructure {
#SerializedName("id")
val id : Int? = null
#SerializedName("value")
val value : List<String> = listOf();
#SerializedName("field_id")
val field_id : String? = null
}
There is a problem with the parameter "value" because it isn't always an array of String, sometimes it could be just a String type.
So when happens I would like to recognise it and create an array of just one String.
depending on what library you use the json parsing it may require a custom parsing type e.g. for kotlinx.serialization you might need to do something like a custom serializer
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#specifying-serializer-on-a-property
better still : tell you server-side developer it should always be an array!

Multi type object in kotlin

from an API call i get as response a body with this structure
open class BaseResponseEntity {
#SerializedName("result")
val result: ResultEnum = ResultEnum.NONE
#SerializedName("errorCode")
val errorCode: String = ""
#SerializedName("errorMessage")
val errorMessage: String = ""
#SerializedName("successMessage")
val successMessage: String = ""
#SerializedName("value")
val code: LastPaymentCodeModel?
}
where the field "value" can be three types: null, String or LastPaymentCodeModel. How can i get this?
I managed to put a ? so that both null and LastPaymentCodeModel are handled, but i don't know how to handle the String type too.
I think the best approach would probably be to use type Any? for code.
Then you should write a custom GSon serializer/deserilizer (JsonDeserializer<BaseResponseEntity>) for the BaseResponseEntity object.
In this Json deserializer, you would need to check the type of value (e.g is it a string or a data structure) and decode it to the correct object type.
Alternative, to avoid the use of Any?, you could leave the model exactly as you have it. You will still need to write a custom JsonDeserializer, however if value is a string then it would still create a LastPaymentCodeModel, using the string value as one of it's properties.

Can't able to store input data in firestore kotlin android

Here is the code
val db = Firebase.firestore
val user = hashMapOf(
"name" = "{binding.edit_name.text.toString()}",
"email" = "{binding.edit_email.text.toString()}
)
binding.submitBtn.setOnClickListener{
db.collection("users").add(user)
.addOnSuccessListener {
Toast.makeText(context,"Data inserted",Toast.LENGTH_LONG).show()
}
.addOnFailureListener {
Toast.makeText(context,"Error",Toast.LENGTH_LONG).show()
}
}
In above code edit_name , edit_text is input taken by keyboard. I can't able to store the user in firestore. I think there is problem of converting bindig.edit_name.text.toString() it can't able to convert string.
If i use hash map without taking input from keyboard as below then I am able to insert data in firestore.
val user = hashMapOf(
"name" to "ABC",
"email" to "abc#gmail.com"
)
This hash map is able to add in firestore.
I think there is problem in binding and I am also can't able to toast bindg.edit_name.toString() as shown below
binding.submitBtn.setOnClickListener{
Toast.makeText(context,"${binding.edit_name.text.toString()},Toast.LENGTH_LONG).show()
}
Please help in this. I think all problem is that I can't able to convert binding data value as a string (by default it is Editable).
According to the official documentation your code seems fine from the point of view of inserting data.
The issue seems indeed to rely on "binding.variable_input.text.toString()".
While investigating the Android Data Binding Library I came across this post, which recommends instead of using toString(), use:
String.valueOf(), or if you want to use toString(), try
Integer.valueOf().toString()
Expressions cannot be used for assignments in HashMap, so probably worth changing hashMapOf( "name" to "{binding.name.text.toString()}", "email" to "{binding.email.text.toString()}")
Better assign val name = binding.name.text.toString() and then hashMapOf( "name" to name)
Even better to create a custom object and set the values.
data class UserInfo (val name: String? = "",val email: String? = "") { constructor(): this("","")}

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.

Categories

Resources