RecyclerView AsyncListDiffer and data source consistency state lose with onClickListener

552 Views Asked by At

I have data source(in that example it's just a var myState: List)

class MainActivity : AppCompatActivity() {
    var generation: Int = 0
    var myState: List<User> = emptyList()
    val userAdapter = UserAdapter {
        val index = myState.indexOf(it)
        if (index == -1)
            println(" not found")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recycler = findViewById<RecyclerView>(R.id.rvContent)
        recycler.layoutManager = LinearLayoutManager(this)
        recycler.adapter = userAdapter
        Thread {
            while (true) {
                generateNewData()
                Handler(mainLooper).post {
                    userAdapter.submit(myState)
                }
                sleep(3000L)
            }
        }.start()

    }

    fun generateNewData() {
        generation++
        myState = (0..5000).map { User("$generation", it) }
    }
}

I have RecyclerView, and AsyncListDiffer connected to it

data class User(val name: String, val id: Int) {
    val createdTime = System.currentTimeMillis()
}

data class UserViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
    fun bindTo(user: User, action: (User) -> Unit) {
        val textView = view.findViewById<TextView>(R.id.title)
        textView.text = "${user.name} ${user.id} ${user.createdTime}"
        textView.setOnClickListener { action(user) }
    }
}

class UserAdapter(val action: (User) -> Unit) : RecyclerView.Adapter<UserViewHolder>() {
    val differ: AsyncListDiffer<User> = AsyncListDiffer(this, DIFF_CALLBACK);

    object DIFF_CALLBACK : DiffUtil.ItemCallback<User>() {

        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        return UserViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = differ.currentList[position]
        holder.bindTo(user, action = action)
    }

    fun submit(list: List<User>) {
        differ.submitList(list)
    }

    override fun getItemCount(): Int {
        return differ.currentList.size
    }
}

I have OnClickListener binded to every item on RecyclerView

{
    val index = myState.indexOf(it)
    if (index == -1)
        println(" not found")}

That listener checks if item that was clicked is exists in the data source, and if not, outputs it to the console. Every few seconds data in the source are changed, and pushed to a AsyncListDiffer via submitList method, some how internally it uses other thread to match data and pass that diffed data to the RecyclerView, and that takes some time; If I starts clicking on the items non-stop, and the click event occurs at the same time when the differ inserts new data, then I get into a non-consistent state.

So, how to handle that?

  1. Ignore a click with inconsistent data?(cons: User can see some strange behaviour like list item not collapse/expand, no navigation happen, etc)
  2. Try to find a similar item in the new data by separate fields(positions/etc), and use it?(cons: same as 1. but less probability)
  3. Block OnClickListener events until the data is consistent in both the Recycler and the data source? (cons: same as above, and also lag with action user performed until data became consistent again)
  4. Something else? What is a best way to solve that?
0

There are 0 best solutions below