[SOLVED] How to properly select and deselect items in recycler view?


This Question and Answer are collected from stackoverflow and tested by JTuto community, is licensed under
CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

Issue

I am very beginner to Android Kotlin, I am developing a practice app which requires a selected data from recycler view to be in an arraylist. Selection works fine until I change my spinner value and items get reloaded, once items get reloaded it take two clicks to deselect but is still not removed from the selected data arrayList. (whenever spinner value is changed, data is loaded from API). I know this code is messy and has a lot of unnecessary stuff, if someone could help me with how to clean the code and solve the issue, will be very glad.

class RecyclerViewAdapter(val dataList:ArrayList<ModelClass>):RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>(){

   object ob {
        val dataSelected= ArrayList<ModelClass>()

   }

private  var _binding: ItemViewBinding? = null
private val binding get() = _binding!!
private lateinit var nListener : onItemClickListener

interface onItemClickListener{
    fun onItemClick(position: Int)

}
fun setOnItemClickListener(listener:onItemClickListener){
    nListener=listener
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewAdapter.ViewHolder {

    val v=LayoutInflater.from(parent.context).inflate(R.layout.item_view,parent,false)
    _binding= ItemViewBinding.bind(v)

    return ViewHolder(binding.root)
}
fun bindItems(data:ModelClass){


    binding.itemQuant.text=data.item_quant
    binding.itemName.text=data.item_name
    binding.mfgName.text=data.mfg
    binding.quantity.text=data.item_stock.toString()



}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.restore()
    bindItems(dataList[position])
    holder.select()



}

@SuppressLint("ResourceAsColor")
inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {

   fun restore(){


       for (i in 0 until ob.dataSelected.size){
           for (j in 0 until dataList.size){
                if (ob.dataSelected[i].sku_code==(dataList[j]).sku_code) {


                        if(adapterPosition == j){

                            itemView.isSelected = true
                            itemView.setBackgroundColor(R.color.black)
                            println("****")

                        }
                }

           }
       }

   }


 fun select(){

        itemView.setOnClickListener {



            val position: Int = adapterPosition
            if (ob.dataSelected.contains(dataList[position]) ){
                    itemView.setBackgroundResource(0)
                    itemView.isSelected = false
                 ob.dataSelected.remove(dataList[position])

                for (i in 0 until ob.dataSelected.size){
                    println(ob.dataSelected[i].sku_code)
                }
            }
            else {
                        itemView.isSelected = true
                        itemView.setBackgroundColor(R.color.black)
                        ob.dataSelected.add((dataList.get(position)))
                for (i in 0 until ob.dataSelected.size){
                    println(ob.dataSelected[i].sku_code)
                }

            }
        }


 }



}


override fun getItemCount(): Int {
    return dataList.size
}
override fun getItemId(position: Int): Long = position.toLong()

} . . . . . . edit:

class RecyclerViewAdapter(val dataList:ArrayList,val onItemClicked: (Int) -> Unit):RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>(){

object ob { val dataSelected= ArrayList()

}

private var checkedPosition = -1


fun setData(listModel: List<ModelClass>) {
    dataList.clear()
    dataList.addAll(listModel)
    notifyDataSetChanged()
}




override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val binding = ItemViewBinding.inflate(
        LayoutInflater.from(parent.context), parent, false
    )
    return ViewHolder(binding, parent.context)
}


override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.bindItems(dataList[position])
}

override fun getItemCount() = dataList.size

inner class ViewHolder(
    val binding: ItemViewBinding,
    val context: Context
): RecyclerView.ViewHolder(binding.root) {
    @SuppressLint("ResourceAsColor", "ResourceType")
    fun bindItems(data: ModelClass) = with(binding) {
        /*
        * Map Your data Here
        * example: tvTitle.text = model.title
        * */
        binding.itemQuant.text=data.item_quant
        binding.itemName.text=data.item_name
        binding.mfgName.text=data.mfg
        binding.quantity.text=data.item_stock.toString()

        when (checkedPosition) {
            -1 -> {
                itemView.setBackgroundResource(0)
            }
            else -> when (checkedPosition) {
                adapterPosition -> {
                    itemView.setBackgroundColor(R.color.black)

                }
                else -> {
                    itemView.setBackgroundResource(0)
                }
            }
        }
        root.setOnClickListener {
           itemView.setBackgroundResource(R.color.black)
            if (checkedPosition != adapterPosition) {
                notifyItemChanged(checkedPosition)
                checkedPosition = adapterPosition
            }

            // Handle the clicked item

            if (ob.dataSelected.contains(dataList[adapterPosition])){
                ob.dataSelected.remove(dataList[adapterPosition])
                itemView.isSelected=false
                for (i in ob.dataSelected){
                    println(i.sku_code)
                }
                itemView.setBackgroundColor(R.color.black)
            }
            else {
                ob.dataSelected.add(dataList[adapterPosition])
                itemView.isSelected=true
                for (i in ob.dataSelected){
                    println(i.sku_code)
                }
                itemView.setBackgroundResource(0)
            }
            onItemClicked.invoke(adapterPosition)
        }
    }
}

}

Solution

here try this one, i tried refactoring your code

class RecyclerViewAdapter(
    val onItemClicked: (Int) -> Unit
) : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {

    private val data = ArrayList<ModelClass>()
    private var checkedPosition = -1 // -1: No Item Selected, 0: First Item Selected

    fun setData(listModel: List<ModelClass>) {
        data.clear()
        data.addAll(listModel)
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemViewBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding, parent.context)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bindItems(data[position])
    }

    override fun getItemCount() = data.size

    inner class ViewHolder(
        val binding: ItemViewBinding,
        val context: Context
    ): RecyclerView.ViewHolder(binding.root) {
        fun bindItems(model: ModelClass) = with(binding) {
            /*
            * Map Your data Here
            * example: tvTitle.text = model.title
            * */

            when (checkedPosition) {
                -1 -> {
                    clLayout.setBackgroundResource(0)
                }
                else -> when (checkedPosition) {
                    absoluteAdapterPosition -> {
                        clLayout.setBackgroundResource(R.drawable.bg_curved_grey)
                    }
                    else -> {
                        clLayout.setBackgroundResource(0)
                    }
                }
            }
            root.setOnClickListener {
                clLayout.setBackgroundResource(R.drawable.bg_curved_grey)
                if (checkedPosition != absoluteAdapterPosition) {
                    notifyItemChanged(checkedPosition)
                    checkedPosition = absoluteAdapterPosition
                }
                // Handle the clicked item 
                onItemClicked.invoke(absoluteAdapterPosition)
            }
        }
    }
}

here how you use it in Your Activity or fragment

private val recyclerViewAdapter: RecyclerViewAdapter by lazy {
    //RecyclerViewAdapter(this::onClickedItem) you can call the function below like this or
    //RecyclerViewAdapter { onClickedItem(it) } or
    RecyclerViewAdapter { position ->
      onClickedItem(position ) 
    }
}

private fun onClickedItem(position: Int) {
    //do something
    Toast.makeText(this, position.toString, Toast.LENGTH_SHORT).show()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    binding.recyclerview.apply {
        layoutManager = LinearLayoutManager(this)
        adapter = recyclerViewAdapter
    }

    // yourdata = dataList:ArrayList<ModelClass>
    recyclerViewAdapter.setData(yourData)
}

some explanation

its a Higher order function, you can read it more here Higher order function

val onItemClicked: (Int) -> Unit

with this piece of code we can remove this

private lateinit var nListener : onItemClickListener

interface onItemClickListener{
    fun onItemClick(position: Int)

}
fun setOnItemClickListener(listener:onItemClickListener){
    nListener=listener
}

with this piece of code

fun setData(listModel: List<ModelClass>) {
    data.clear()
    data.addAll(listModel)
    notifyDataSetChanged()
}

we make the adapter flexible with any data set List<ModelClass>

Answered By – Rio Wirawan

people found this article helpful. What about you?