4

I'm trying to fetch all messages data inside my ChatMessage data class from my Firebase Database. In the logs, I can see that data is indeed fetched inside my Snapshot, but it isn't getting assigned to my variables in the data class. I'm new to Kotlin & Firebase, so I don't really understand why is it happening?

After I did some logs searching I found out that I have been receiving this warning:

W/ClassMapper: No setter/field for -Llk34LtqGPJ3bwPrYRi found on class com.idiotboxes.socialowl.models.ChatMessage

Did some searching for this error here & found it is a very common problem indeed. But my issue, none of the solutions have a data class format like mine.

I understand that the warning is saying I need to setup getters & setters in my data class, but I'm fairly new to Kotlin & I don't know how to implement getter-setters in my data class.

Here's how my data class ChatMessage looks like:

package com.idiotboxes.socialowl.models

data class ChatMessage(val messageID: String, val fromID: String, val toID: 
String,val message: String, val timeStamp: Long){

    //No argument constructor
    constructor(): this("","","","",-1)
}

Here is how my Firebase Database node looks like: Database Structure

EDIT: Here's extra code to help you understand where the problem could be

My Callback interface

interface FirebaseCallback {

    fun onCallback(latestMessage: ChatMessage)

}

My function which reads the Data from Firebase

private fun readFirebaseData(firebaseCallback: FirebaseCallback){
    //Signed In user's ID
    val fromID = FirebaseAuth.getInstance().uid
    Log.w(TAG, "from ID is: $fromID (Currently Signed-In user)")

    //Get Database Reference
    val msgRef = FirebaseDatabase.getInstance().getReference("/Latest-messages/$fromID"/* Reference to current user's database entry */)

    //Set a Listener for new messages
    msgRef.addChildEventListener(object: ChildEventListener {
        //adds new row when someone messages you for the first time, i.e. New Child is Added
        override fun onChildAdded(p0: DataSnapshot, p1: String?) {
            Log.w(TAG, "Snapshot captured from Firebase Database is: $p0")
            //Convert snapshot to messages
            //val latestMessage = p0.getValue(ChatMessage::class.java) ?: return
            val callbackData: ChatMessage = p0.getValue(ChatMessage::class.java) ?: return
            //TODO Implement Callback
            firebaseCallback.onCallback(callbackData)
        }

        //Updates existing rows latest messages when user receives new message i.e. Latest Message child is Changed.
        override fun onChildChanged(p0: DataSnapshot, p1: String?) {
            val latestMessage: ChatMessage = p0.getValue(ChatMessage::class.java) ?: return//If Null then return

            //Update the Existing row with new message
            latestMessagesHashMap[p0.key!!] = latestMessage
            updateRecyclerView()

        }
    --Some redundant methods of ChildEventListener--
    })
    latest_messages_recycler_view.adapter = adapter
    //Recycler View Bottom Line Border
    latest_messages_recycler_view.addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
}

Function where I attempt to retrieve data from my callback

private fun showLatestMessages(){

readFirebaseData(object : FirebaseCallback{
        override fun onCallback(latestMessage: ChatMessage) {
            //TODO latestMessage is  not null. But blank values are  being filled  in the Chat Message model class
            Log.w(TAG, "NotNull latestMessage values are fromID: ${latestMessage.fromID} toID: ${latestMessage.toID} Typed Message: ${latestMessage.message} TimeStamp: ${latestMessage.timeStamp}")

            //Add the new message row
            adapter.add(LatestMessagesItems(latestMessage, context ?: return))
        }
    })

    //Set OnClick Listener on Recycler View Items
    adapter.setOnItemClickListener { item, view ->
        //Grab the User details from the item that is clicked.
        val userItem = item as LatestMessagesItems

        //Start Chat Activity with the clicked User
        val startChat = Intent(activity, ChatActivity::class.java)
        startChat.putExtra(USER_KEY, userItem.recipientUser)
        startActivity(startChat)
    }
}

private fun updateRecyclerView(){
    //Clear existing rows
    adapter.clear()
    //Fetch all Latest Messages from HashMap
    latestMessagesHashMap.values.forEach {
        adapter.add(LatestMessagesItems(it, context?: return))
    }
}

My messages item for my recyclerview

class LatestMessagesItems(val latestMessage: ChatMessage, val ctx: Context): Item<ViewHolder>(){

lateinit var recipientUser: User

override fun getLayout(): Int {
    return R.layout.latest_message_row
}

override fun bind(viewHolder: ViewHolder, position: Int) {

    Log.w(TAG, "Fetched latest Message is: $latestMessage")
    //Null Check
    val recipientID: String? = if (latestMessage.fromID == FirebaseAuth.getInstance().uid) {
        latestMessage.toID
    } else {
        latestMessage.fromID
    }

    //Fetch the recipient user details
    val fetchRef = FirebaseDatabase.getInstance().getReference("/users/$recipientID")
    fetchRef.addListenerForSingleValueEvent(object: ValueEventListener{
        override fun onDataChange(p0: DataSnapshot) {
            Log.w(TAG, "Fetched Recipient ID is: $recipientID")
            //Fetch user details in User model class
            recipientUser = p0.getValue(User::class.java) ?: return
            Log.w(TAG, "Fetched Recipient User Name is: ${recipientUser.username}")

            //Fill the User Details
            viewHolder.itemView.recipient_username.text = recipientUser.username //Username
            Glide.with(ctx).load(recipientUser.profileImage).into(viewHolder.itemView.recipient_profile_image) //Profile Pic
        }

            override fun onCancelled(p0: DatabaseError) {

            }
        })

        //Latest Message Received
        viewHolder.itemView.latest_received_message.text = latestMessage.message
    }
}

And Finally I updated my Model Class according to some of the suggestions posted here. ChatMessage.kt

package com.idiotboxes.socialowl.models

data class ChatMessage(var messageID: String? = null, var fromID: String? = null , var toID: String? = null ,var message: String? = null , var timeStamp: Long? = null){
    //No argument constructor
    constructor(): this("","","","",-1)
}

Yet, the problem still persists.

Rishabh More
  • 85
  • 4
  • 11
  • I don't know Firebase, but until someone more knowledgeable comes along… You might try making the class `open`.  Some frameworks (like Spring) rely on creating subclasses at runtime so they can add functionality.  (In fact, there's a Maven plug-in which automatically makes all relevant classes open.)  What makes me suspicious here is that your class clearly doesn't have a field called `-Llk34LtqGPJ3bwPrYRi`… – gidds Aug 10 '19 at 11:04
  • Please edit the question to show the code of the database that fails. That is the first problem - you are trying to deserialize a DataSnapshot that does not look like User object. The second problem, which you haven't encountered yet, is that your data class properties must all be `var` with default values in order for Kotlin to generate setter methods for each of them. – Doug Stevenson Aug 10 '19 at 16:59
  • @gidds @DougStevenson I've updated my question with more relevant code that you can look at. Regarding what the value `-Llk34LtqGPJ3bwPrYRi` is, _It is the Key, of the latest message_ that is being updated into the database structure. And if you look again at the screenshot of my database that I've posted in my question, you can see I've set the value of the key to the field `messageID` which _does exist in my model._ So please have a look at my code again. Thank you. – Rishabh More Aug 11 '19 at 05:57
  • Yeah, so, you need to dig into that child snapshot more than you are now. You'll have to first iterate the children at the location of the snapshot, then load them into User objects. – Doug Stevenson Aug 11 '19 at 06:03
  • @DougStevenson As I've mentioned before I'm new to Kotlin & Firebase. From what I can understand from your suggestion that I should directly get reference to my -Llk34LtqGPJ3bwPrYRi node, so it will get all the values of it's children inside it into my Model class, right? Thing is idk how to get my database reference path directly to the key. cos it involves traversing through two uniquely generated user ID's & a unique key for the message. Could you look at my code & suggest where & how should I change the database reference so that it directly gets to the key node? – Rishabh More Aug 11 '19 at 08:31

1 Answers1

3

I can't say for sure it's the case but I think that you should write to variables (var), not values (val).

This is because Kotlin automatically generates getters/setters for var, which is considered mutable, and doesn't for val which is considered immutable.

The same happens for private vars as Kotlin doesn't provide getters/setters for them outside their local scope (e.g. for Firebase to access them).

schlenger
  • 1,447
  • 1
  • 19
  • 40