Sergei Mikhailovskii
Sergei Mikhailovskii

Reputation: 2465

How to delete items from recyclerview which uses diffutil?

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?

Upvotes: 6

Views: 6886

Answers (2)

Orestis Gartaganis
Orestis Gartaganis

Reputation: 607

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)

Upvotes: 2

Blackbelt
Blackbelt

Reputation: 157487

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

Upvotes: 8

Related Questions