Search code examples
androidkotlinandroid-recyclerviewduplicatesandroid-edittext

Another "RecyclerView duplicating items" question


I'm having this problem and spent hours exploring different solutions found here but couldn't figure it out. I have a RecyclerView with a RadioGroup (with two RadioButton) and an EditText. As expected, the text keeps getting duplicated on scroll and the "original" gets deleted. The same happens with the radio buttons. I've tried to save on another array backup the values when the view is recycled but couldn't solve the duplicating issue.

Here's my adapter

class ServicesCheckoutAdapter(var context: Context,
                              var servicesList: List<Service>) : RecyclerView.Adapter<ServicesCheckoutAdapter.ViewHolder>() {

    private lateinit var onRadioGroupClickListener: OnRadioGroupClickListener

    private lateinit var onTextChangedListener: OnTextChangedListener

    private lateinit var onServiceClickListener: OnServiceClickListener
    private var externalArray = mutableListOf<String>()


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

        val view = LayoutInflater.from(context).inflate(R.layout.services_list_item,
                parent, false)

        val viewHolder = ViewHolder(view)

        val position = viewHolder.adapterPosition

        view.setOnClickListener {

            if (onServiceClickListener != null) {
                onServiceClickListener.onServiceClick(view, servicesList[position].id, position)
            }
        }

        return viewHolder
    }

    override fun getItemId(position: Int): Long {
        return super.getItemId(position)
    }

    override fun getItemViewType(position: Int): Int {
        return super.getItemViewType(position)
    }

    interface OnServiceClickListener {
        fun onServiceClick(view: View, serviceId: Int, position: Int)
    }

    fun setOnServiceClickListener(listener: OnServiceClickListener)
    {
        onServiceClickListener = listener
    }

    interface OnRadioGroupClickListener {
        fun onRadioGroupClick(buttonId: Int, serviceId: Int, position: Int) {}
    }

    fun setOnRadioButtonClickListener(listener: OnRadioGroupClickListener) {
        onRadioGroupClickListener = listener
    }

    interface OnTextChangedListener{
        fun onTextChanged(position: Int, text: String)
    }

    fun setOnTextChangedListener(listener: OnTextChangedListener){
        onTextChangedListener = listener
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        Log.d("recycler", "lista: ${servicesList[position].serviceSolution}")

        holder.edtSolution.removeTextChangedListener(holder.watcher)

        holder.bind(context,
                servicesList[position].id,
                servicesList[position].name,
                servicesList[position].serviceSolved,
                servicesList[position].serviceSolution,
                onRadioGroupClickListener,
                onTextChangedListener,
                position)

    }

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


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

        var radioGroup = itemView.findViewById<RadioGroup>(R.id.radioGroupService)
        val edtSolution = itemView.findViewById<EditText>(R.id.editTextCheckoutDesc)
        var watcher: TextWatcher? = null


        fun bind(context: Context,
                 serviceId: Int,
                 serviceName: String,
                 serviceSolved: Boolean,
                 serviceSolution: String,
                 onRadioGroupClickListener: OnRadioGroupClickListener,
                 onTextChangedListener: OnTextChangedListener,
                 position: Int
        ) {

            itemView.findViewById<TextView>(R.id.serviceTitle)
                    .text = context.resources
                    .getString(R.string.service_title_comma, serviceName)

            itemView.findViewById<RadioGroup>(R.id.radioGroupService)
                    .setOnClickListener {
                        onRadioGroupClickListener
                                .onRadioGroupClick(
                                        (it as RadioGroup).checkedRadioButtonId, serviceId, adapterPosition)
                    }



            if (serviceSolved) {
                radioGroup.find<RadioButton>(R.id.radioBtnYes).isChecked = true


                radioGroup.find<RadioButton>(R.id.radioBtnNo).isChecked = false
            } else {

                radioGroup.find<RadioButton>(R.id.radioBtnYes).isChecked = false


                radioGroup.find<RadioButton>(R.id.radioBtnNo).isChecked = true
            }


            edtSolution.addTextChangedListener(object: TextWatcher{
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

                }

                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                    onTextChangedListener.onTextChanged(position, s.toString())

                }

                override fun afterTextChanged(s: Editable?) {
                }

            })

        }

    }
}

And here's the adapter initialization on the activity

serviceList = occurrence.services

        servicesAdapter = ServicesCheckoutAdapter(this, serviceList)

        recyclerViewServices.adapter = servicesAdapter
        recyclerViewServices.layoutManager = LinearLayoutManager(this)

        servicesAdapter.setOnRadioButtonClickListener(object : ServicesCheckoutAdapter.OnRadioGroupClickListener {

            override fun onRadioGroupClick(buttonId: Int, serviceId: Int, position: Int) {
                super.onRadioGroupClick(buttonId, serviceId, position)

                when (buttonId) {
                    R.id.radioBtnYes -> {
                        serviceList[position].serviceSolved = true
                        servicesAdapter.notifyDataSetChanged()
                    }

                    R.id.radioBtnNo -> {
                        serviceList[position].serviceSolved = false
                        servicesAdapter.notifyDataSetChanged()
                    }
                }
            }
        })

        servicesAdapter.setOnTextChangedListener(object : ServicesCheckoutAdapter.OnTextChangedListener{
            override fun onTextChanged(position: Int, text: String) {
                serviceList[position].serviceSolution = text
            }

        })

Solution

  • I've tried to use a ListView but the keyboard went crazy changing focus. Decided to keep the RecyclerView and since my dynamic list isn't so large, I used the

    holder.setIsRecyclable(false);
    

    on the OnBindViewHolder() method and it solved my issue. Thanks everyone for the help