Home > Mobile >  FirestoreRecyclerAdapter with Custom onItemClicked Implementation throws IndexOutOfBoundsException
FirestoreRecyclerAdapter with Custom onItemClicked Implementation throws IndexOutOfBoundsException

Time:07-30

I'm trying to have a FirestoreRecyclerAdapter update a list of chats in an Activity without needing to restart the Activity then allow the user to click on an item in the list and use specific information gathered using the clicked index to start an Activity where the user can chat.

My custom onItemClicked implementation works fine and everything works as intended if the activity is reloaded; if the user is already in the Activity and the FirestoreRecyclerAdapter dynamically updates without the activity restarting clicking on the new item causes an IndexOutOfBoundsException to be thrown.

My adapter.

class ChatListAdapter(options: FirestoreRecyclerOptions<UserProfile>
    ) : FirestoreRecyclerAdapter<UserProfile, ChatListAdapter.UserHolder>(options) {
    private var clickListener: ClickListener? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatListAdapter.UserHolder {
        val view = LayoutInflater.from(parent.context).inflate(
            android.R.layout.simple_list_item_1,
            parent, false
        )
        return UserHolder(view)
    }

    override fun onBindViewHolder(holder: UserHolder, position: Int, model: UserProfile) {
        val user: UserProfile = getItem(position)
        holder.itemView.setOnClickListener {
            clickListener!!.onItemClicked(position)
        }
        holder.bind(user)
    }

    inner class UserHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private var userNameText: TextView

        init {
            userNameText = itemView.findViewById<View>(android.R.id.text1) as TextView
        }

        fun bind(user: UserProfile) {
            userNameText.text = user.userName
        }
    }

    fun setOnItemClickListener(clickListener: ClickListener) {
        this.clickListener = clickListener
    }

    interface ClickListener {
        fun onItemClicked(position: Int)
    }
}

Relevant snippet of code in my Activity.

val query = fsDb.collection("groups")
    .document(userId!!).collection("userGroups")
val options = FirestoreRecyclerOptions.Builder<UserProfile>()
    .setQuery(query, UserProfile::class.java).build()

chatListAdapter = ChatListAdapter(options)
chatsRecyclerView.layoutManager = LinearLayoutManager(this)
// The RecyclerView itemAnimator needs to be set to null otherwise it
// will throw an out of bounds exception
chatsRecyclerView.itemAnimator = null
chatListAdapter!!.startListening()
chatsRecyclerView.adapter = chatListAdapter

val uIRef = fsDb.collection("users").document(userId!!)
uIRef.get().addOnCompleteListener {
    if (it.isSuccessful) {
        val doc = it.result
        if (doc.exists()) {
            val fromUser = doc.toObject(UserProfile::class.java)!!

            val groupsRef = fsDb.collection("groups").document(fromUser.uId)
                .collection("userGroups")

            groupsRef.get().addOnCompleteListener { p ->
                if (p.isSuccessful) {
                    val currentChatNames = ArrayList<String>()
                    val userList = ArrayList<UserProfile>()
                    val groupsList = ArrayList<String>()
                    for (i in p.result) {
                        val contact = i.toObject(UserProfile::class.java)
                        if (contact.uId != fromUser.uId) {
                            currentChatNames.add(contact.userName)
                            userList.add(contact)
                            // Document id works as the group id
                            groupsList.add(i.id)
                        }
                     }
                            
                     chatListAdapter!!.setOnItemClickListener(object : 
                         ChatListAdapter.ClickListener {
                         override fun onItemClicked(position: Int) {
                             val intent = Intent(this@ChatListActivity, 
                                 ChatActivity::class.java)
                             intent.putExtra("fromUser", fromUser)
                             intent.putExtra("toUser", userList[position])
                             intent.putExtra("roomId", groupsList[position])
                             startActivity(intent)
                         }
                     })

                } else {
                    println("Getting list of current chats was unsuccessful")
                }
            }
        }
    }
}

CodePudding user response:

My solution was to abandon the FirestoreRecyclerAdapter and instead use a combination of addSnapshotListener and a ListView.

The following code replaced my Activity code snippet and does exactly what I want it to.

val currentChatNames = ArrayList<String>()
val userList = ArrayList<UserProfile>()
val groupsList = ArrayList<String>()
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,
    android.R.id.text1, currentChatNames)

chatsListView.adapter = adapter

val ref = fsDb.collection("groups")
    .document(userId!!).collection("userGroups")

// Listen for changes in current chats user is a part of
ref.addSnapshotListener {snapshot, e ->
    if (e != null) {
        Log.w(TAG, "Listen failed.", e)
        return@addSnapshotListener
    }
    if (snapshot != null) {
        Log.d(TAG, "Current data size: ${snapshot.size()}")
        for (i in snapshot.documents) {
            val contact = i.toObject(UserProfile::class.java)
            if (contact!!.uId != fromUser!!.uId) {
                currentChatNames.add(contact.userName)
                userList.add(contact)
                // Document id works as the group id
                groupsList.add(i.id)
            }
         }
         adapter.notifyDataSetChanged()
     } else {
         Log.d(TAG, "Current data: null")
     }
}

chatsListView.setOnItemClickListener { parent, view, position, id ->
    val intent = Intent(this@ChatListActivity, ChatActivity::class.java)
    intent.putExtra("fromUser", fromUser)
    intent.putExtra("toUser", userList[position])
    intent.putExtra("roomId", groupsList[position])
    startActivity(intent)
}
  • Related