Parsing boolean from Firebase Database Snapshot - android

I'm having issues pasing a Snapshot from the Firebase Realtime Database into a Data Class in Kotlin using ProGuard.
Here's how the data looks in the Firebase console:
Here's how I've modeled that data class In my android app:
data class PickupCode(
val code: String,
val boxId: String,
val orderId: String,
val suborderId: String,
val drawers: List<Int>,
val isDelivered: Boolean
) {
constructor(): this("", "", "","", emptyList(), false)
override fun toString(): String {
return code
}
}
Here's how I build the database request:
val reference = database.getReference("pickupCodes/$boxId/$code")
val listener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
println(snapshot)
val pickupCode = snapshot.getValue<PickupCode>(PickupCode::class.java)
pickupCode?.let {
println("Code: ${it.code}, is delivered: ${it.isDelivered} to drawers: ${it.drawers.toString()}")
if (!it.isDelivered) {
// No success
} else {
// Success!
}
} ?: run {
// No success
}
} else {
// No success
}
}
override fun onCancelled(error: DatabaseError) {
// No success
}
}
This is what the println(snapshot) line prints:
DataSnapshot { key = 320625, value = {isDelivered=true, code=320625, drawers={0=2}, orderId=-LhdzXS4-gyT0ysNe-zi, suborderId=-LhdzYhT78y9b3iJcyrb, boxId=box_1} }
And this is what the next print 3 lines lates prints:
Code: 320625, is delivered: false to drawers: [2]
Here I would expect is delivered to be true, but for some reason true-value of isDelivered from the snapshot, is ignored when parsing the snapshot into a PickupCode-class. The value isDelivered of the PickupCode, is equal to the empty constructor of the class.
But WHY and HOW to fix?
All the other values from the snapshot gets parsed corrently. I'm new to Android, but I have a hunch that ProGuard (whatever that is) has some of the blame here.. Here's how I've set it up:
-keepattributes Signature
-keepclassmembers class PickupCode.** {
*;
}

I found a solution to this..
When inspecting the verbose logs I found this:
W/ClassMapper: No setter/field for isDelivered found on class com.x.y.models.PickupCode
After playing a bit around, I found that for some weird reason, the setters for properties starting with is are ignored :S I tested with other property names as well and types.. fx. val isBerp: Number gets the same warning.
So after changing the property name from isDelivered to delivered in both the class and firebase, it works..
I wasn't able to find documentation for this behaviour, so if someone knows about it, it would appreciate a link..

Related

How to get value from firebase realtime database in android?

This is the picture of my firebase database I am trying to store "brand" as an arrayList.
Below is my code
var brandArrayList= ArrayList<BrandModel>()
database= FirebaseDatabase.getInstance()
mRef=database.getReference("brand")
mRef.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
for (childSnapshot in snapshot.children) {
val getBrand= childSnapshot.getValue<BrandModel>(BrandModel::class.java)
getBrand!!.item_id=childSnapshot.key
brandArrayList.add(getBrand!!)
}
}
}
override fun onCancelled(error: DatabaseError) {
Log.i("Error",error.toString())
}
})
I am getting error at the line
val getBrand= childSnapshot.getValue(BrandModel::class.java)
I have defined my Model classes as given below
#Parcelize
data class BrandModel(var item_id:String?=null,
var name:String?=null,
var sizeinfo:List<SizeModel>?=null):Parcelable
#Parcelize
data class SizeModel(var quantity:String?=null,
var name:String?=null):Parcelable
I am getting this error
com.google.firebase.database.DatabaseException: Expected a List while
deserializing, but got a class java.util.HashMap
A List in Kotlin code translates to the following structure in the database:
"sizeinfo": {
"0": { ... },
"1": { ... },
"2": { ... },
}
Only when you have sequential numbers in the keys will Firebase convert it to a List. When the keys are of a different format, such as in your case, it gets converted into a Map.
So you'll either need to change the data structure, or (more likely) change the code to match the JSON: Map<String, SizeModel>.

How to propperly exclude the Document Id property when saving a custom class to Firestore?

My custom class is designed to store the Document Id besides the relevant data.
data class Appointment(
#DocumentId
val UID: String,
val date: Timestamp,
val header: String
) {
companion object {
fun DocumentSnapshot.toAppointment() : Appointment? {
try {
val date = getTimestamp("date") ?: Timestamp(Date())
val header= getString("header").toString()
return Appointment(id, date, header)
} catch (e: Exception) {
Log.e(TAG, "Error converting appointment", e)
return null
}
}
}
}
Reading a document to my class is no problem due to my custom toAppointment function, unfortunately I can't prevent firestore to write the UID data to firestore using the add() function:
val appointment = Appointment("", Timestamp(Date()), "Header")
docRef.collection("appointments").add(appointment)
In Firestore the newly created document always have an UID-field (with an empty string).
I added the #DocumentId annotation as mentioned here - unfortunately this seems to only work with the set() function.
Thank you very much

Firebase getValue returns as a HasMap with the key regardless of how I ask for it

Some of my data is stored in Firebase as like in the screen shot bellow, under a parent called "votes":
I am trying to pull the value only (-1) but keep getting the whole HashMap.
The key in this case is represented in my code as a variable called inititorId and postVotesSnapshot represents the parent snapshot the holds many children as in the screen shot I've attached.
I've tried:
postVotesSnapshot.child(initiatorId).value
or
postVotesSnapshot.child(initiatorId).getValue(Integer::class.java)
And both got me the whole HashMap with the key causing a crash because I need the value to be an Int.
I've tried:
val valueHash = postVotesSnapshot.child(initiatorId).getValue(HashMap::class.java)
val myValue = valueHash[initiatorId]
But that doesn't work wither.
I'm not sure what has gone wrong as the code worked perfectly before with the first option I've mentioned and today it suddenly throws an error at me.
Here's the complete Listener:
val refVotes = if (postType == 0) {
FirebaseDatabase.getInstance().getReference("/questions/$mainPostId/main/votes")
} else {
FirebaseDatabase.getInstance().getReference("/questions/$mainPostId/answers/$specificPostId/votes")
}
refVotes.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(postVotesSnapshot: DataSnapshot) {
setVotesCount(specificPostId, mainPostId, votesView, postType)
if (postVotesSnapshot.hasChild(initiatorId)) {
val voteValue = postVotesSnapshot.child(initiatorId).getValue(Integer::class.java) //this line is the problematic one
//I do stuff
}
}
})
}
Try the following:
val ref = firebase.child("posts")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot?) {
val id = dataSnapshot.child(initiatorId).getValue(Integer::class.java)
}
override fun onCancelled(error: FirebaseError?) {
println(error!!.message)
}
})
Assuming, you have the following database:
posts
VMQPBq6YK3bJ12xIjGeTHsqaJC2 : -1
Here the dataSnapshot will be at child posts, then you need to attach the addListenerForSingleValueEvent and access the child initiatorId. Also assuming that initiatorId is equal to VMQPBq6YK3bJ12xIjGeTHsqaJC2

How to create a right JSON class in Kotlin (for Fuel)

I have a request that returns JSON:
{
"success": 0,
"errors": {
"phone": [
"Incorrect phone number"
]
}
}
I plugged Fuel instead of Retrofit for Kotlin. So, my classes are:
data class RegistrationResponse(
val success: Int,
val errors: RegistrationErrorsResponse?) {
class Deserializer : ResponseDeserializable<RegistrationResponse> {
override fun deserialize(content: String): RegistrationResponse? =
Gson().fromJson(content, RegistrationResponse::class.java)
}
}
data class RegistrationErrorsResponse(val phone: List<String>?) {
class Deserializer : ResponseDeserializable<RegistrationErrorsResponse> {
override fun deserialize(content: String): RegistrationErrorsResponse? =
Gson().fromJson(content, RegistrationErrorsResponse::class.java)
}
}
A request looks like:
class Api {
init {
FuelManager.instance.basePath = SERVER_URL
}
fun registration(name: String, phone: String): Request =
"/registration/"
.httpPost(listOf("name" to name, "phone" to phone))
}
private fun register(name: String, phone: String) {
Api().registration(name, phone)
.responseObject(RegistrationResponse.Deserializer()) { _, response, result ->
val registrationResponse = result.component1()
if (registrationResponse?.success == 1) {
showScreen()
} else {
showErrorDialog(registrationResponse?.errors?.phone?.firstOrNull())
}
}
}
A problem is that when error occurs, phone variable in data class (registrationResponse?.errors?.phone) is filled with null, but not "Incorrect phone number".
After searching through Fuel issues I understood that in most cases we don't need to write own deserializators as they are already written by Gson.
In https://github.com/kittinunf/Fuel/issues/265 there is an example. So, just put your data class inside <>:
URL.httpPost(listOf("name" to name, "phone" to phone)).responseObject<RegistrationResponse> ...
and get data through
result.component1()?.errors?.phone?.firstOrNull()
Old version of answer
Probably an obstacle is a list deserialization, see
1. https://github.com/kittinunf/Fuel/issues/233 and
2. https://github.com/kittinunf/Fuel/pull/236.
I think, by default Fuel doesn't use Gson deserialization.
I still don't know how to deserialize a list, But got values with this expression:
((result.component1().obj()["errors"] as JSONObject).get("phone") as JSONArray)[0]

Android Room error: TypeConverter not recognised for List of Enums

The Room library is not recognizing a TypeConverter I created for a List of enums. However, when I change this to an ArrayList of enums it works fine. Anyone has any idea why and what can I do to make this work with List? (Using List in Kotlin is easier and I really don't wanna be converting back and forwards to ArrayList just because of this).
Here is my code:
My model:
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: List<DayOfWeek>?)
DayOfWeek is an enum:
enum class DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
val value: Int
get() = ordinal + 1
companion object {
private val ENUMS = DayOfWeek.values()
fun of(dayOfWeek: Int): DayOfWeek {
if (dayOfWeek < 1 || dayOfWeek > 7) {
throw RuntimeException("Invalid value for DayOfWeek: " + dayOfWeek)
}
return ENUMS[dayOfWeek - 1]
}
}
}
My TypeConverter:
private const val SEPARATOR = ","
class DayOfWeekConverter {
#TypeConverter
fun daysOfWeekToString(daysOfWeek: List<DayOfWeek>?): String? {
return daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)
}
#TypeConverter
fun stringToDaysOfWeek(daysOfWeek: String?): List<DayOfWeek>? {
return daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }
}
}
And I set it in my DB class like this:
#Database(entities = arrayOf(Example::class), version = 1)
#TypeConverters(DayOfWeekConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun exampleDao(): ExampleDao
}
My DAO looks like this:
#Dao
interface ExampleDao {
#Query("SELECT * FROM example")
fun getAll(): LiveData<List<Example>>
#Insert(onConflict = REPLACE)
fun save(examples: List<Example>)
}
The error I get with this code is:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
e:
e: private java.util.List<? extends com.example.DayOfWeek> days;
Like I said above, if I change the days property to ArrayList<DayOfWeek> (and make the changes to ArrayList in DayOfWeekConverter) then everything works fine. If anyone can help me figure this out and tell me how I can use List here it'd be of great help, it is driving me crazy :/.
For some reason, Room does not like Kotlin List, but when I've replaced List with MutableList it started to work:
#Entity
data class Example(#PrimaryKey val id: String,
val name: String,
var days: MutableList<DayOfWeek>?)
class DayOfWeekConverter {
companion object {
#TypeConverter
#JvmStatic
fun daysOfWeekToString(daysOfWeek: MutableList<DayOfWeek>?): String? =
daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)
#TypeConverter
#JvmStatic
fun stringToDaysOfWeek(daysOfWeek: String?): MutableList<DayOfWeek>? =
daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }?.toMutableList()
}
}
This is not perfect solution, but hope you can investigate more with that.
Also you need to change #PrimaryKey to be not nullable
The full signature of List in Kotlin is List<out E>(List<? extend E> in Java), it doesn't make any sense to convert such generic type. In other words, Room doesn't know if the input is a DayOfWeek or its subclass.
As for ArrayList and MutableList, their full signature is ArrayList<E> and MutableList<E> correspondingly, the input type is fixed and hence Room knows how to convert it.
We have no way to store and get a List enum without array list. Room does not support it. But if you want to avoid using array list, you can create an object ListDayOfWeek with List is an attribute. I tried it and it's ok. If you need code, please reply here. I will post it.
You should not store it like that into your database. Better build something like that and store it as int:
enum class DaysOfWeek(var bitValue: Int) {
Monday(64),
Tuesday(32),
Wednesday(16),
Thursday(8),
Friday(4),
Saturday(2),
Sunday(1);
}
Finally your utility functions to convert from/to enum.
fun daysToBitValue(days: EnumSet<DaysOfWeek>): Int {
var daysBitValue = 0
for (`val` in days) daysBitValue += `val`.bitValue
return daysBitValue
}
private fun fromBitValues(origBitMask: Int): EnumSet<DaysOfWeek> {
val ret_val = EnumSet.noneOf(DaysOfWeek::class.java)
var bitMask = origBitMask
for (`val` in DaysOfWeek.values()) {
if (`val`.bitValue and bitMask == `val`.bitValue) {
bitMask = bitMask and `val`.bitValue.inv()
ret_val.add(`val`)
}
}
if (bitMask != 0) {
throw IllegalArgumentException(String.format(Locale.getDefault(), "Bit mask value 0x%X(%d) has unsupported bits 0x%X. Extracted values: %s", origBitMask, origBitMask, bitMask, ret_val))
}
return ret_val
}
Now you can either store an int and get the weekdays later:
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: Int = 0)
or you use the enum and write a proper typeconverter for that.
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: EnumSet<DaysOfWeek>?)
class DayOfWeekConverter {
#TypeConverter
fun daysOfWeekToString(daysOfWeek: EnumSet<DayOfWeek>?) =
daysOfWeek?.let{ YourUtilities.daysToBitValue(it) }
#TypeConverter
fun intToDaysOfWeek(daysOfWeek: Int?) =
daysOfWeek?.let { YourUtilities.fromBitValues(it) }
}
You can loop through the enum and get all days by using
for (aDaysOfWeekEnumSet in daysOfWeekEnumSet)
info{ "day = ${aDaysOfWeekEnumSet.name"}
Code is untested because im not on my Computer, but this should show the concept which works better then storing an enum (which is just an alias to a predefined value!)

Categories

Resources