androidarraylistandroid-recyclerviewandroid-livedataandroid-binding-adapter

Adding Some View to RecyclerView item programmatically creates an known bug


So I,m creating a todo app where user can set priority and priority stars are added programmatically through binding adapter but when scrolling the tasks recycler view Tasks with zero no of stars are also showing stars wheres checking Log it shows zero stars. Also this bug occurs only when i scroll to item with stars and then start scrolling rigorously

  1. App just started

enter image description here

  1. Scrolled to Item with tasks Star

enter image description here

  1. After Scrolling rigorously

enter image description here

I have tried Creating New ArrayList everyTime item is updated

Fragment Code :

viewModel.taskCategory.observe(viewLifecycleOwner, Observer { taskCategoryPair ->
            taskCategoryPair?.let {
                Log.i("HomeFragment","Submitting New List")
                viewModel.filterDataFinish()
                adaptor.submitList(it)
            }
        })

RecyclerView bind code :

 RecyclerView.ViewHolder(binding.root) {
        fun bind(
            task: ToDo,
            category: Category,
            clickListener: MiscellaneousUtils.GetIdFromClick
        ) {
            binding.task = task
            binding.category = category
            binding.clickListener = clickListener
            binding.onCheckChangeListener = categorizedTasksClickListeners

            binding.root.setOnLongClickListener {
                categorizedTasksClickListeners.onLongClickItem(adapterPosition,it)
                true
            }

            binding.executePendingBindings()
        }

BindingAdapter function which add stars :

@BindingAdapter("app:setPriority")
fun LinearLayoutCompat.setPriority(task: ToDo) {
    if (task.priorityStars == 0)
        return

    val previousStarContainer = findViewWithTag<LinearLayout>("starContainerLayout")

    if (previousStarContainer != null) {
        removeView(previousStarContainer)
    }

    val starContainer = LinearLayout(context)
    starContainer.tag = "starContainerLayout"
    starContainer.layoutParams = LinearLayoutCompat.LayoutParams(
        ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT
    )

    for (count in 1..task.priorityStars) {
        val starView = ImageView(context)
        starView.setImageResource(R.drawable.task_star)
        starView.layoutParams = LinearLayoutCompat.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        starContainer.addView(starView)
    }
    addView(starContainer)

}

Solution

  • Recycler recycle views :) - It uses same view to show multiple data! That means that a view can be used to show an object with stars - so on onBindViewHolder you show the stars. And then the same view (with stars now) will be used to show a different object (no star object) - it means that on onBindViewHolder you will now need to hide the stars, which I bet you didn't ;)

    As I said, if you have no stars you don't remove the previous starts :

    if (task.priorityStars == 0)
            return
    

    Change your code to:

    @BindingAdapter("app:setPriority")
    fun LinearLayoutCompat.setPriority(task: ToDo) {
        val previousStarContainer = findViewWithTag<LinearLayout>("starContainerLayout")
    
        if (previousStarContainer != null) {
            removeView(previousStarContainer)
        }
    
        if (task.priorityStars == 0)
            return
    
        val starContainer = LinearLayout(context)
        starContainer.tag = "starContainerLayout"
        starContainer.layoutParams = LinearLayoutCompat.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
    
        for (count in 1..task.priorityStars) {
            val starView = ImageView(context)
            starView.setImageResource(R.drawable.task_star)
            starView.layoutParams = LinearLayoutCompat.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            starContainer.addView(starView)
        }
        addView(starContainer)
    
    }