How to find invalid Kotlin object generated by Gson (from reflection)? - android

{
"data":[{"compulsory_field": 1}, {"compulsory_field": 2}, {}]
}
converts into object by gson
data class Something(val compulsory_field: Int)
val somethingList = //gson parse the data
println(somethingList)
//[
// Something(compulsory_field = 1),
// Something(compulsory_field = 2),
// Something(compulsory_field = null) //Should not exists
//]
and I want to get rid of the 3rd item. Is it possible to do it AFTER it has been converted to object? Or can it only be done when it's String/InputStream? And how can I do that?
Thanks!
Edit: clarify that the constructor works, but gson failed to understand kotlin rules and injected objects that I can't check in Kotlin

I came up with a ugly "solution"/workaround, but I am still searching for a better answer (or get the project to switch to moshi codegen or something else, whichever comes first)
Basically I just copy each object again to make sure it goes through all the null-safety checking kotlin provides
val somethingList = //gson parse the data
val fixedSomethingList = somethingList.mapNotNull {
try {
it.copy(compulsory_field = it.compulsory_field)
} catch (e: IllegalArgumentException) { //if gson inserted a null to a non-null field, this will make it surface
null //set it to null so that they can be remove by mapNotNull above
}
}
Now the fixedSomethingList should be clean. Again, very hacky, but it works......

If you don't like empty objects then just remove them. You should always can do it after parsing. However please be aware that in Kotlin lists can be mutable or not. It you received an inmutable one (built with "listOf") then you will have to build a new list including only the elements you want.
https://kotlinlang.org/docs/reference/collections.html
Edited: Ok, I understand you can't even parse the json in first place. In this case maybe you can try this:
To allow nulls: change type of compulsory_field from Int to Int? at declaration
Fix your json string before parsing: In your case you can replace all {} with {"compulsory_field": null}
Now gson parser should obtain a valid list.

Related

Android kotlin assigning a list row to variable error

I am trying to update the view state in the view model using a row from a list object
val update = getCurrentViewStateOrNew()
val updatedRow = update.editLog?.distortion?.get(0)?.copy(
allOrNothing = allOrNothing ?: null,
)
update.editLog?.distortion?.get(0) = updatedRow
setViewState(update)
On the second last line the IDE has underlined the .get(0) in red stating "variable expected". I do not know what it wants from me or how to get this error to go away. I'm simply trying to update the current view state.
You are trying to set a value using a getter method.
You need to get the list, update the first index, then set that updated list to the ViewState.
val update = getCurrentViewStateOrNew()
val updatedRow = update.editLog?.distortion?.get(0)?.copy(
allOrNothing = allOrNothing ?: null,
)
val distortions: ArrayList<Distortion> = ArrayList()
update.editLog?.distortion?.let{ list ->
distortions.addAll(list)
}
distortions.set(0, updatedRow)
update.editLog?.distortion = distortions
setViewState(update)
Kotlin has special operators for collections called get and set.
Using get you can only return a value.
Using set you can only set a value.
It means you cannot assign a new value to the result returned by the get operator.
What you need to do instead is either use set operator which accepts two arguments: index of the element and the element itself.
update.editLog?.distortion?.set(0, updatedRow)
Alternative is to use brackets on an unwrapped array, making sure it is not null:
update.editLog?.distortion?.let {
it[0] = updatedRow
}
Read more here about updating lists.
Read more here about retrieving values from a list.
You're trying to make an assignment to the return value of the get method.
You probably meant to set that element of the list:
update.editLog?.distortion?.set(0, updatedRow)
You can't use the normal [] syntax due to it being nullable.

add/overwrite field of type array in Firestore

I want to add a field of type array inside a collection.
if the field doesn't exist create it. if it exists overwrite it with the new array value.
the field should be called macAddress and it's of type array of String
I have tried the following:
val macInput = setting_mac_text.text.toString()
val macArray = macInput.split(",")
val macList = Arrays.asList(macArray)
val data =
hashMapOf(Pair(FirebaseConstants.USER_MAC_ADDRESS, macArray))
//save it in firebase
db.collection(FirebaseConstants.ORGANIZATION)
.document(orgID + ".${FirebaseConstants.USER_MAC_ADDRESS}")
.set(FieldValue.arrayUnion(macList))
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "successfully inserted")
} else {
Log.d(TAG, " failed ${task.exception}")
}
}
also tried to insert the list itself and hash map like this
val data = hashMapOf(Pair(FirebaseConstants.USER_MAC_ADDRESS, macArray))
db.collection(FirebaseConstants.ORGANIZATION)
.document(orgID)
.set(data))
but it keeps giving me java.lang.IllegalArgumentException: Invalid data. Nested arrays are not supported
what am I doing wrong here?
You're doing three things wrong here:
FieldValue.arrayUnion() is only meant to be used as the value of a field to add elements to that field. The way you are using it now in the first sample, it's being taken as the entire contents of the document.
set() with one parameter is only intended to create or overwrite an entire document. It can't be used to update an existing document. You would have to pass in SetOptions to tell it to merge if you want an update. Or, you would simply use update() to modify an existing document.
Your code that deals with macArray and macList isn't working the way you expect. You are creating a list with one element, which is itself an array. The error message is telling you that you can't have nested arrays like this.
I suggest taking a step back and simplifying your code, removing all the moving parts that don't have to do with Firestore. Just hard code values in your Firestore update until the update works the way you want, then add in the code that works with actual values. Get one simple thing to work, then add to it. If you get an error, you will know that the code you just added was incorrect.
To overwrite an array, you would simply call the set method and have the merge option set to true:
try {
const query = await DatabaseService.queryBuilder({
collection: CollectionName,
});
return await query
.doc(insuranceId)
.set(
{ DOCUMENT_PROPERTY_HERE: ARRAY_HERE },
{ merge: true }
);
} catch (exception) {
return Promise.reject(exception);
}

Change item's data type in a List or array in Kotlin

how can i change item's data type in arrays or lists in kotlin ?
i found a usual way but i need an easier and faster and better way to change data type of an array :)
fun typeChanger (data:MutableList<Number>): DoubleArray {
val result = mutableListOf<Double>()
for (i in data.iterator()){
result.add(i.toDouble())
}
return result.toDoubleArray()
}
val x = mutableListOf<Number>(+1,+1,-1,-1)
val xx:DoubleArray = typeChanger(x) // It works but i need an easier and faster and better way :)
Array map is your friend. You could keep your function and simplify, or remove it completely as below:-
val xx = x.map { it.toDouble() }
Once it's a list of doubles, you can then leave as a list or use .toDoubleArray() if you need an array.

Android Room - How to migrate when a nested object model is changed?

I have a database schema set up using Android Room, Dao, and Entity classes set up as POJOs. Except the POJO entity isn't so "plain" and that it actually holds a reference to another object. I thought this was a great idea at the time as it allowed me more flexibility in changing the object and using it in other places in the app and only saving to the database as needed.
The problem I'm facing now is that the migration guideline only mentions how to migrate the database by altering the SQL, but I changed the object itself. My typeconverter class simply converts the object to and from a string.
Because it's being saved as a long string I know I essentially have to do a simple REPLACE(string, old_string, new_string) in the SQL
migration code block with the updated object being the new string. How can I retrieve the old objects and update values before running the replace SQL command in the migration block?
UPDATE: I'm using GSON in my typeconverter class to change the object to a string, so the solution that comes to mind is to simply download the old object and upload the new one with the added fields. Only problem is that you can't access the database and download the json, convert it to the object, add the new data fields, then reconvert to a new json string.
I'm lucky I'm not at scale yet because this would be a tricky thing to do for so many users. (So I recommend that anyone reading this not do what I did and implement object nesting. It's easier to convert the Entry objects to the other portable objects instead of nesting when it comes to updating the data you want saved.)
I think if you already did what I did and can't go back, the best bet is to simply create the new portable object and make new typeconverter functions for that one and add the SQL COLUMN for the new object. The problem then lies in how you then retrieve those objects from the Entry Dao, which will cause a lot more code to write and possible errors to debug if not done carefully.
Long story short, if anyone is reading this, DO NOT nest objects in Room DBs on Android unless you are 100% sure it's a final form of your model... but is there such a thing anyways?
I just ran into this issue, but fortunately I only needed to add a new key/value pair to a "flat" object model. So hopefully my answer can be expanded on to fully answer #Mr.Drew question.
Assuming you have a table town with a column star_citizen that is the object model being typeconverted:
{"name":"John", "age":30, "car":false}
and you want to update the object to have an extra property "house": true
you could add a migration to your App's Room Database class like this (Kotlin example):
#Database(entities = [Town::class], version = 2, exportSchema = true)
#TypeConverters(DataConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract val sharedDao : SharedDao
companion object {
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val cursor = database.query("SELECT * FROM `town`")
// iterate through each row in `town`, and update the json
// of the StarCitizen object model
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val colIdIdx = cursor.getColumnIndex("id")
val id = cursor.getInt(colIdIdx)
val colStarCitizenIdx = cursor.getColumnIndex("star_citizen")
val rawJson = cursor.getString(colStarCitizenIdx)
val updatedRawJson = starCitizenModelV1ToV2(rawJson)
database.execSQL("""UPDATE town SET star_citizen ='${updatedRawJson}' WHERE ID = $id""")
cursor.moveToNext()
}
}
}
//[...]
private fun starCitizenModelV1ToV2(rawJson: String): String {
val rawJsonOpenEnded = rawJson.dropLast(1)
val newProperty = "\"house\":true"
return "$rawJsonOpenEnded,$newProperty}"
}
}
}

Why my data class giving null values unless i applied default on it?

I am trying to show data from the server using data class of kotlin. It's almost working fine but some case whenever I fetch a response I don't know why it is still giving null values unless I add a default value ("") for msg.
This is my data class
data class ViewcardModel(
val msg: String = "", // here is default values
val cartcnt: String = "",
val order_total: Int = 0,
val status: Boolean = false
)
This is my response from server
{
status = false // server response
}
You're probably using something like GSON to instantiate the instances of your model. These tools use reflection to create instances, and therefore the default parameter value of your primary constructor will never take effect (since it's never called).
What you need is the same as what this question is about:
Setting Default value to a variable when deserializing using gson
A custom deserializer is probably what you'll end up with.
You should pass the value into data class like this
val s= ViewcardModel(status=false)
println(s.toString())
val s1= ViewcardModel(msg="hello",status=false)
println(s1.toString())
output
ViewcardModel(msg=, cartcnt=, order_total=0, status=false)
ViewcardModel(msg=hello, cartcnt=, order_total=0, status=false)

Categories

Resources