Search code examples
androidkotlinandroid-recyclerviewandroid-adapterandroid-viewholder

Strange behaviour happen in onBindViewHolder android kotlin


Hey I am working in android kotlin. I am working in reyclerview. I want to do single selection in my items. I tried some code which is working fine.

OptionsViewAdapter.kt

class OptionsViewAdapter : ListAdapter<ProductVariant, OptionsViewHolder>(PRODUCT_COMPARATOR) {
    private var selectedItemPosition: Int = 0

    companion object {
        private val PRODUCT_COMPARATOR = object : DiffUtil.ItemCallback<ProductVariant>() {
            override fun areItemsTheSame(
                oldItem: ProductVariant,
                newItem: ProductVariant
            ): Boolean {
                return oldItem == newItem
            }

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionsViewHolder {
        return OptionsViewHolder.bindView(parent)
    }

    override fun onBindViewHolder(holder: OptionsViewHolder, position: Int) {
        holder.binding.root.setOnClickListener {
            selectedItemPosition = holder.bindingAdapterPosition
            notifyAdapter()
        }
        val drawableColor = if (selectedItemPosition == position)
            R.drawable.options_item_selected_background
        else
            R.drawable.options_item_default_background

        holder.binding.root.background =
            ContextCompat.getDrawable(holder.binding.root.context, drawableColor)
        holder.bindItem(getItem(position), position)
    }

    fun notifyAdapter() {
        notifyDataSetChanged()
    }
}

OptionsViewHolder.kt

class OptionsViewHolder(
    val binding: OptionsItemLayoutBinding,
) : RecyclerView.ViewHolder(binding.root) {

    companion object {
        fun bindView(parent: ViewGroup): OptionsViewHolder {
            return OptionsViewHolder(
                OptionsItemLayoutBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                )
            )
        }
    }

    fun bindItem(item: ProductVariant?, position: Int) {

    }
}

Video for single click is working fine.

When I move onBindViewHolder code inside bindItem it not working. Can someone guide me why this is happening.

vs

OptionsViewAdapter.kt

class OptionsViewAdapter : ListAdapter<ProductVariant, OptionsViewHolder>(PRODUCT_COMPARATOR) {

    companion object {
        private val PRODUCT_COMPARATOR = object : DiffUtil.ItemCallback<ProductVariant>() {
            override fun areItemsTheSame(
                oldItem: ProductVariant,
                newItem: ProductVariant
            ): Boolean {
                return oldItem == newItem
            }

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionsViewHolder {
        return OptionsViewHolder.bindView(parent)
    }

    override fun onBindViewHolder(holder: OptionsViewHolder, position: Int) {
        holder.bindItem(getItem(position), position ,::notifyAdapter)
    }

    fun notifyAdapter() {
        notifyDataSetChanged()
    }
}

OptionsViewHolder.kt

class OptionsViewHolder(
    val binding: OptionsItemLayoutBinding,
) : RecyclerView.ViewHolder(binding.root) {

    private var selectedItemPosition: Int = 0

    companion object {
        fun bindView(parent: ViewGroup): OptionsViewHolder {
            return OptionsViewHolder(
                OptionsItemLayoutBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                )
            )
        }
    }

    fun bindItem(
        item: ProductVariant?,
        position: Int,
        onAdapterChange: () -> Unit
    ) {
        binding.root.setOnClickListener {
            selectedItemPosition = position
        onAdapterChange()

        }
        val drawableColor = if (selectedItemPosition == position)
            R.drawable.options_item_selected_background
        else
            R.drawable.options_item_default_background

        binding.root.background =
            ContextCompat.getDrawable(binding.root.context, drawableColor)
    }
}

The video for single click is not working.


Solution

  • You moved the selectedItemPosition into the ViewHolder class, so you have a separate copy of this property for every instance of the ViewHolder class so you are no longer affecting other items in the list when any one list item is clicked.

    This would be much easier to implement by making the view holder class an inner class of the Adapter so it can directly modify the adapter’s selectedItemPosition property. And I would give the property a custom setter so you can automatically notify the adapter of the change instead of having to work with a separate function call. You can also make it notify the adapter specifically of which two row items changed instead of the whole data set (more efficient and by doing it in the property setter you have access to the old and new row positions in one place easily—field and value).

    private var selectedItemPosition: Int = 0
        set(value) {
            val oldPos = field
            field = value
            notifyItemChanged(oldPos)
            notifyItemChanged(value)
        }