Search code examples
androidkotlinonclicklistenerrace-condition

How can I prevent a race condition in Android RecyclerView's Adapter?


I have a classic implementation of a recycler view that, when I click on an item inside the recycler view, that item gets deleted.

The problem is that, when I successively click twice one after another (without any noticeable delay between the clicks) on an item in that recycler view, then the second click on that same item is registered at a different position.

The way I identify the item that received the click is by holder.adapterPosition (where holder is an instantiation of ViewHolder class). I wonder if I'm doing wrong by relying on this.

To further troubleshoot, I added the following println statement to troubleshoot:

println("layoutpos ${holder.layoutPosition} adapterpos ${holder.adapterPosition} oldpos ${holder.oldPosition}")

Then, as I repeated those successive clicks, I got the following output in Android Studio's Run tab:

[Galaxy_Nexus_API_22 [emulator-5554]]: I/System.out: layoutpos 1 adapterpos 1 oldpos -1
[Galaxy_Nexus_API_22 [emulator-5554]]: I/System.out: layoutpos 0 adapterpos -1 oldpos -1

Right now, my thoughts are: use adapterPosition, and ignore it when its value is -1 (assume that -1 means a declaration of a racing condition). But I feel that I might be missing something deeper.

How should I handle this situation?


Solution

  • I found two solutions:

    if (holder.adapterPosition == -1) return // Race condition; do nothing
    // else, do stuff
    

    This does the trick. However, it is not elegant in my view, as: why receive clicking events to begin with if we are not supposed to? It doesn't seem to be solving the problem from its roots.

    To solve it more elegantly (from its roots), I did this in the setOnClickListener:

    holder.item.setOnClickListener {
        // we don't want 2nd click right?  so let's delete the listener
        holder.item.setOnClickListener{}
    
        /* then, do the stuff for this listener.  this stuff
           will be done once, as we deleted its listener earlier,
           so, subsequent clicks are not possible. */
    }
    

    This way, the item with that listener is clicked on once, and a second click does not happen to begin with, hence a racing condition is not possible from its roots. Because the clicking listener is deleted right when the first click is received. Should I want to allow the item to get clicks again, I can redefine a listener for it again.