How to delete items from recyclerview which uses diffutil?

Viewed 4696

I have the following recyclerview adapter:

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.add_new_card_list_item.view.*
import kotlinx.android.synthetic.main.bank_card_list_item.view.*
import kz.moneyman.R

class BankCardsAdapter(
        private val onDeleteCardClickListener: OnDeleteCardClickListener,
        private val onAddCardClickListener: OnAddCardClickListener
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var checkedItem = 0
    private val differ = AsyncListDiffer(this, BankCardsDiffUtilCallback())

    override fun getItemViewType(position: Int): Int {
        if (differ.currentList[position].isNotEmpty()) {
            return R.layout.bank_card_list_item
        }
        return R.layout.add_new_card_list_item
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)

        return if (viewType == R.layout.bank_card_list_item) {
            BankCardViewHolder(itemView)
        } else {
            AddNewCardViewHolder(itemView)
        }
    }

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

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (differ.currentList[position].isNotEmpty()) {
            (holder as BankCardViewHolder).bindData(differ.currentList[position], onDeleteCardClickListener)
        } else {
            (holder as AddNewCardViewHolder).bindData(onAddCardClickListener)
        }
    }

    fun setData(cardNumbers: ArrayList<String>) {
        differ.submitList(cardNumbers)
    }

    fun deleteElement(position: Int) {
        differ.currentList.removeAt(position)
        notifyDataSetChanged()
    }

    interface OnDeleteCardClickListener {
        fun onDeleteCardClicked(position: Int, value: String)
    }

    interface OnAddCardClickListener {
        fun onAddCardClicked()
    }

    inner class BankCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindData(value: String, onDeleteCardClickListener: OnDeleteCardClickListener) {

            itemView.card_number_tv.text = value
            itemView.card_firm_iv.setImageResource(if (value[0] == VISA_START_SYMBOL) R.drawable.ic_visa else R.drawable.mastercard)

            itemView.delete_card_tv.setOnClickListener {

                if (adapterPosition != RecyclerView.NO_POSITION) {
                    onDeleteCardClickListener.onDeleteCardClicked(adapterPosition, value)
                }

            }

            itemView.bank_card_rb.setOnCheckedChangeListener(null)

            itemView.bank_card_rb.isChecked = checkedItem == adapterPosition
            itemView.bank_card_rb.setOnCheckedChangeListener { _, isChecked ->
                if (isChecked) {
                    checkedItem = adapterPosition
                    notifyDataSetChanged()
                }
            }

            itemView.bank_card_cl.setOnClickListener {
                checkedItem = adapterPosition
                notifyDataSetChanged()
            }


        }

    }

    inner class AddNewCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindData(onAddCardClickListener: OnAddCardClickListener) {

            itemView.online_add_card_tv.setOnClickListener {

                if (adapterPosition != RecyclerView.NO_POSITION) {
                    onAddCardClickListener.onAddCardClicked()
                }

            }

        }

    }

    companion object {

        private const val VISA_START_SYMBOL = '4'
        private const val MASTERCARD_START_SYMBOL = '3'

    }

}

So, as you can see, I set data in method setData and then data is passed to diffutil, where the computations take place. But then I need to remove an element of the differ list in adapter. For this purpose I created method deleteElement, which removes an element at position. But when I run the app, I catch the following exception:

java.lang.UnsupportedOperationException
        at java.util.Collections$UnmodifiableList.remove(Collections.java:1359)
        at views.adapters.BankCardsAdapter.deleteElement(BankCardsAdapter.kt:54)
        at TestStepFragment$onViewCreated$1.onDeleteCardClicked(TestStepFragment.kt:36)
        at BankCardsAdapter$BankCardViewHolder$bindData$1.onClick(BankCardsAdapter.kt:76)
        at android.view.View.performClick(View.java:6597)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access$3100(View.java:778)
        at android.view.View$PerformClick.run(View.java:25885)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

As I understand, the problem is that differ.currentList is immutable list, but how can I solve this problem?

2 Answers

you have to keep copy of the original data, remove the item from there and then call differ.submitList. DiffUtils will do the rest for you

You must not remove it from within the adapter, but from your Activity/Fragment/any_ui_element you're using.

If you're supplying a list, then you will edit the supplied list from your UI (simplified version):

class Fragment() {
var listSupplied = mutableListOf(1,2,3)
var adapter : BankCardsAdapter? = null

override fun onCreate() {
...
adapter = BankCardsAdapter(
    onDeleteCardClickListener{ deleteElement(element) },
    onAddCardClickListener{ clickElement(element) }
)
adapter.submitList(listSupplied)
...
}
private fun deleteElement(element: Element) {
    listSupplied.remove(element)
    adapter.notifyDatasetChanged()
}

However, I would advise you to use ListAdapter and then take advantage of its amazing methods for adding/removing specific elements, which will also add some animations to it and also save on computations as it doesn't edit the whole list but just the requested elements.

Take a look here: https://medium.com/simform-engineering/listadapter-a-recyclerview-adapter-extension-5359d13bd879

After you do that, what you'll need to call is:

adapter?.notifyItemRemoved(viewHolder.adapterPosition)

Related