1

I am trying to get unique numbers from call logs of a user and show them in a RecyclerView. By unique I mean if I have shown the number, or corresponding contact once in the list, I have to skip the item. And I want to use a Cursor for showing this to make the rendering quick.

To do this, I have implemented a RecyclerView Adapter which looks like this (Only showing the relevant code, kotlin)

class CallAdapter(val activity: Activity, var cursor: Cursor) : RecyclerView.Adapter<CallAdapter.ViewHolder>() {


        val handler = Handler()
        val contacts = ArrayList<Contact>()

        init {
            cursor.moveToFirst()
            UniqueLogs(this).start()
        }

        companion object {
            const val TAG = "CallAdapter"
        }

        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
            val inflater = LayoutInflater.from(activity)
            val binding = CallLogItemBinding.inflate(inflater, parent, false)
            return ViewHolder(binding)
        }

        override fun getItemCount(): Int {
            return contacts.count()
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val contact = contacts[position]

            ...

            cursor.moveToNext()
        }

        class ViewHolder(val binding: CallLogItemBinding) : RecyclerView.ViewHolder(binding.root)

        class UniqueLogs(val adapter: CallLogAdapterShobhit) : Thread() {
            private var available = true
            private val cursor = adapter.cursor
            private val hash: HashMap<String, Int> = HashMap<String, Int>()
            private val contacts = adapter.contacts
            private var count = 0

            override fun run() {
                cursor.moveToFirst()
                while (available && cursor.moveToNext()) {
                    var contact = adapter.getCurrentItemContact()

                    while (available && hash[contact.phoneNumber] == 1) {
                        if (cursor.moveToNext()) {
                            contact = adapter.getCurrentItemContact()
                        } else {
                            available = false
                        }
                    }

                    if (available) {
                        contacts.add(contact)
                        val position = count
                        count += 1

                        adapter.handler.post({
                            Log.d(TAG, "Position: $position Size: ${adapter.itemCount}")
                            adapter.notifyItemInserted(position)
                        })

                        hash[contact.phoneNumber] = 1
                    }
                }

            }
        }
    }

When I run this code, I am getting an exception which looks like this :-

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{c75176c position=5 id=-1, oldPos=4, pLpos:4 scrap [attachedScrap] tmpDetached not recyclable(1) no parent}

The position, id, oldPos, pLpos, etc. are different every time but the error is essentially the same.

I've tried writing logs as well to make sure that I am not trying to insert an element before the element is added to contacts array list, but the logs say that all's well.

Any idea what might be wrong here?

Shobhit
  • 647
  • 1
  • 7
  • 19

2 Answers2

0

You are updating the data in a background thread, but notifying on the main thread. By the time the notification happens, you may have further altered the data. I would suggest that you do all your updates on the background and then just do a generic notifyDataSetChanged when everything is done.

Another option would be to calculate the entire new list in a background thread and then update the real data once and notify in the main thread.

cwbowron
  • 1,015
  • 6
  • 10
  • I don't want to do that. I wouldn't need a Thread if I wanted to bring all the data in memory together and then render them. I need to sort of "lazy load" contacts. – Shobhit Feb 22 '18 at 12:57
0

Modifying the data and calling notifyItemInserted should synchronously or you will have inconsistent data.

Putting contacts.add(contact) just before notifyItemInserted should solve it.

                    adapter.handler.post({
                        Log.d(TAG, "Position: $position Size: ${adapter.itemCount}")
                        contacts.add(contact)
                        adapter.notifyItemInserted(position)
                    })
Vedant Agarwala
  • 18,146
  • 4
  • 66
  • 89