himansh1608
himansh1608

Reputation: 53

Android Recycler Adapter position not working properly: onClick handling doesn't work properly

I have a recycler view where I fetch details from my database. An ArrayList of FriendEntity is given by the room database which works fine. I want to first have an unchecked box image for the whole list, and if I tap the image, it should change into a checked box image. But when I am achieving this, when I click on 1st item of recyclerView, my 16th item of recyclerView gets changed from unchecked to checked itself and the same goes with the 2nd and 17th, 3rd, and 18th and so on. I don't know if this is a problem with a recycler view adapter or something else.

Data Class FriendEntity :

     @Entity(tableName = "Friends")
     data class FriendEntity (
     @PrimaryKey val friend_name: String ,
     @ColumnInfo(name = "debt") val debt: Float )

ExpenseActivity.xml for RecyclerView:

       <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

ExpenseActivity.kt:

    class ExpenseActivity : AppCompatActivity() {
    private lateinit var binding: ActivityExpenseBinding
    private lateinit var layoutManager: LinearLayoutManager
    private lateinit var recyclerAdapter: FriendListAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this@ExpenseActivity,R.layout.activity_expense)
        layoutManager = LinearLayoutManager(this@ExpenseActivity)
        val friendsList = GetFriends(applicationContext).execute().get()
        binding.txtDef.visibility = if(friendsList.size==0) View.VISIBLE else View.GONE
        binding.recyclerList.visibility = if(friendsList.size!=0) View.VISIBLE else View.GONE
        recyclerAdapter = FriendListAdapter(this@ExpenseActivity,friendsList)
        binding.recyclerList.layoutManager = layoutManager
        binding.recyclerList.adapter = recyclerAdapter
     } }

I have tested my GetFriends it works fine.

FriendListAdapter.kt:

    class FriendListAdapter(val context: Context, private val itemList: ArrayList<FriendEntity>):
    RecyclerView.Adapter<FriendListAdapter.FriendViewHolder>() {
    private var checkList: ArrayList<FriendEntity> = arrayListOf()

    class FriendViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val txtFriendName: TextView = view.findViewById(R.id.txtFriendName)
        val imgCheck: ImageView = view.findViewById(R.id.imgCheck)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FriendViewHolder {
        val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.recycler_single_friend,parent,false)
        return FriendViewHolder(view)
    }
    override fun getItemCount() = itemList.size
    override fun onBindViewHolder(holder: FriendViewHolder, position: Int) {
        val itemObject = itemList[position]
        holder.txtFriendName.text = itemObject.friend_name
        holder.imgCheck.setOnClickListener {
            if(checkList.contains(itemObject)) {
                holder.imgCheck.setImageResource(R.drawable.ic_cb_empty)
                checkList.remove(itemObject)
            } else {
                holder.imgCheck.setImageResource(R.drawable.ic_cb_full)
                checkList.add(itemObject)
            }
        }
     } }

The problem I get is :

I tap on 1st element

The 16th automatically changes

I tap 2nd image

The 17th gets automatically clicked

Also, the behavior is so much problematic that when I tap on the 16th image, it doesn't respond.2nd time clicking makes it unchecked. Same with the 17th image. I guess there is some problem with the adapter.

Then I tried having a small view for individual items so that my screen gets a total of 20 items altogether. Now when I tap on the 1st image, the 24th gets selected, 2nd click changes the 25th. It means it clicks items who are not in the view right now but how?

I paste a GDriveLink for the video here if anyone doesn't understand it till now: Link to video of the problem

I want to know how to remove this error?

Upvotes: 1

Views: 687

Answers (2)

himansh1608
himansh1608

Reputation: 53

The answer provided by Aashit Shah is a good answer surely and would do the work perfectly. But just in case, if anyone doesn't wanna change the entity class (in my case, I didn't want to do any changes in my POJO/data class because I am sending whole object of FriendEntity to Room Database) so you can have a work around.

You can add an Array of Boolean as a member variable of the adapter class like this

private val boolArray = ArrayList(Collections.nCopies(itemCount,false))

This boolArray will keep records of boolean status locally. And hence we can use it onBindViewHolder in the same manner as above:

override fun onBindViewHolder(holder: FriendViewHolder, position: Int) {
    val itemObject = itemList[holder.adapterPosition]
    holder.txtFriendName.text = itemObject.friend_name
    holder.imgCheck.setImageResource(if(boolArray[holder.adapterPosition]) R.drawable.ic_cb_full else R.drawable.ic_cb_empty)
    holder.imgCheck.setOnClickListener {
        if(boolArray[holder.adapterposition]) {
            holder.imgCheck.setImageResource(R.drawable.ic_cb_empty)
            checkList.remove(itemObject)
            boolArray[holder.adapterPosition] = false
        } else {
            holder.imgCheck.setImageResource(R.drawable.ic_cb_full)
            checkList.add(itemObject)
            boolArray[holder.adapterPosition] = true
        }
    }
}

The advantage of using this work around is that it won't change your previous database and this boolArray will be having a lifecycle of just an object and hence space efficiency.

Upvotes: 1

Aashit Shah
Aashit Shah

Reputation: 578

the problem you are facing is due to the reuse of view holders. The simplest solution would be to create a boolean field in you POJO class FriendEntity .

data class FriendEntity (
@PrimaryKey val friend_name: String ,
@ColumnInfo(name = "debt") val debt: Float 
val isSelected  //To store current status)

Now check this status while binding your data and set the checkbox according to it.

override fun onBindViewHolder(holder: FriendViewHolder, position: Int) {
val itemObject = itemList[position]
//Image selection will be done at binding.
if(itemObject.isSelected){
  holder.imgCheck.setImageResource(R.drawable.ic_cb_full)
}else{
  holder.imgCheck.setImageResource(R.drawable.ic_cb_empty)
}
holder.txtFriendName.text = itemObject.friend_name
holder.imgCheck.setOnClickListener {
   //Just change the status and notify the change.
    itemObject.isSelected = !itemObject.isSelected
    notifyItemChanged(position)
}

Upvotes: 4

Related Questions