12

Hi I have a Kotlin data class as follows

data class User (
        @get:Exclude val gUser: Boolean,
        @get:Exclude val uid: String,
        @get:PropertyName("display_name") val displayName: String,
        @get:PropertyName("email") val email: String,
        @get:PropertyName("account_picture_url") val accountPicUrl: String,
        @get:PropertyName("provider") val provider: String
)

I am able to serialize the object without an issues. But i'm having trouble deserializing the object when doing a firebase query. Currently this is what i'm doing to get the data

_firebaseReference.child(getString(R.string.firebase_users_key)).child(user.uid)
        .setValue(user).addOnCompleteListener{
    _firebaseReference.child("users").child(user.uid)
            .addListenerForSingleValueEvent(object : ValueEventListener {
        override fun onCancelled(p0: DatabaseError) {

        }

        override fun onDataChange(p0: DataSnapshot) {
            if (p0.exists()) {
                val userHash = p0.value as HashMap<*, *>
                var currentUser: User
                if (userHash[getString(R.string.provider_key)]
                        != getString(R.string.provider_google)) {
                    currentUser = User(false, p0.key!!, 
                            userHash["display_name"].toString(), 
                            userHash["email"].toString(),
                            userHash["account_picture_url"].toString(), 
                            userHash["provider"].toString())
                } else {
                    currentUser = User(true, p0.key!!, 
                            userHash["display_name"].toString(), 
                            userHash["email"].toString(), 
                            userHash["account_picture_url"].toString(), 
                            userHash["provider"].toString())
                }
            }
        }

    })
}

This is only a test project that i'm working on to practice my Kotlin, but this is something I would like to figure out.

If i'm doing it completely wrong please let me know, any advise would be greatly appreciated

Thanks

aidan8181
  • 175
  • 1
  • 3
  • 7
  • What is the error that you get? – Alex Mamo Jun 06 '18 at 14:59
  • Sorry the above code works but its just messy, if I try deserializing using getValue(User::class.java) i get the error: User does not define a no-argument constructor If i add a second constructor then i'm unable to use the keys for the values because i'm unable to annotate the second constructor – aidan8181 Jun 06 '18 at 15:00
  • Why do say is messy? It isn't. – Alex Mamo Jun 06 '18 at 15:02
  • I'm used to C# and using Newtonsoft library which allows me to annotate my properties for my data classes and I was hoping i could find a similar solution – aidan8181 Jun 06 '18 at 15:07

3 Answers3

29

Firebase needs an empty constructor to be able to deserialize the objects:

data class User(
        @Exclude val gUser: Boolean,
        @Exclude val uid: String,
        @PropertyName("display_name") val displayName: String,
        @PropertyName("email") val email: String,
        @PropertyName("account_picture_url") val accountPicUrl: String,
        @PropertyName("provider") val provider: String
) {
    constructor() : this(false, "", "", "", "", "")
}

You can either declare it like so and provide some default values to be able to call the primary constructor or you can declare default values for all your parameters:

data class User (
        @Exclude val gUser: Boolean = false,
        @Exclude val uid: String = "",
        @PropertyName("display_name") val displayName: String = "",
        @PropertyName("email") val email: String = "",
        @PropertyName("account_picture_url") val accountPicUrl: String = "",
        @PropertyName("provider") val provider: String = ""
)

Then various constructors will be created for you, including an empty constructor.

If there's a problem with serialization there might be because of the getters and setters generated by the ide, try reinforcing them with @get and @set annotations:

data class User (
        @Exclude val gUser: Boolean = false,
        @Exclude val uid: String = "",
        @set:PropertyName("display_name") 
        @get:PropertyName("display_name")
        var displayName: String = "",
        @PropertyName("email") val email: String = "",
        @set:PropertyName("account_picture_url")
        @get:PropertyName("account_picture_url")
        var accountPicUrl: String = "",
        @PropertyName("provider") val provider: String = ""
)
Levi Moreira
  • 11,917
  • 4
  • 32
  • 46
  • Hi thanks for getting back to me the only problem I have with this solution is when I call p0.getValue(User::class.java) the json for display name and account picture url does not deserialize into the object because there names on the database do not match the names of the properties, any idea how to get around this without having to rename the database nodes? – aidan8181 Jun 06 '18 at 17:44
  • 1
    Thanks man i forgot about var and val so that's why i was having trouble worked perfectly thanks!! – aidan8181 Jun 06 '18 at 18:22
  • Hi, @LeviMoreira, can I ask you for some help on my question, its here: https://stackoverflow.com/questions/54454616/kotlin-firebase-datasnapshot-getvalue-does-not-set-properties-on-init-body – MetaSnarf Jan 31 '19 at 06:58
1

What I actually wanted is a Kotlin data class which is derived from a domain model interface like so

data class Dto(@PropertyName("serialized_title") val override title: String) : DomainModel

In this case DomainModel is defined this way

interface DomainModel { val title: String }

My goal was to fetch data from Firestore and get deserialized Dto objects which are provided to clients which receive objects of type DomainModel. So this solution above unfortunately didn't work. I saw the workarounds using @get: and @set: Annotations but I wanted my data class properties to be immutable. Simply using vars is a bad design decision in my use case. And also this solution looks quite ugly...

After inspecting the decompiled Java-Code I came up with this solution

data class Dto(
@field:[JvmField PropertyName("serialized_title")]
override val title: String = "") : DomainModel

The decompiled Java-Code simply uses title as public final field having the PropertyName annotation.

I prefer this solution since it doesn't violate certain design decisions I made...

Dharman
  • 30,962
  • 25
  • 85
  • 135
postfixNotation
  • 181
  • 1
  • 5
0

In Android Studio (kotlin) use this (only var and getter and setter):

  • @set:PropertyName("email") @get:PropertyName("email") var emailPerson: String = ""

None of this works:

  • @PropertyName("email") var emailPerson: String = ""
  • @PropertyName("email") val emailPerson: String = ""
  • @get:PropertyName("email") val emailPerson: String = ""

Android Studio 4.1.2. Gradle: com.google.firebase:firebase-database:19.6.0