Home > Enterprise >  I need help implementing notifyItemChanged for my recyclerview
I need help implementing notifyItemChanged for my recyclerview

Time:05-09

I am trying to set a background color for 1 item in my recyclerview when I click on a button. I get the index of the particular item and try to use a function that includes notifyItemChange in the onclick method of my button in Activity. But it keeps changing the color of the first item in the recyclerview even if I hard code an index number into the function. I know that I am getting the correct index of the selected item, but I'm missing something. Perhaps some code in the adapter? I cannot glean enough information from other answers to figure out how this should be done. Any help? Thanks!

Variable for selectedPosition in Activity:

private var newSelectedPosition: Int = 0

Button onClick in Activity:

R.id.btn_add_daily_report_submit -> {
             Log.e("tag", "$newSelectedPosition")
             updateSingleItem(newSelectedPosition)
            }

Function updateSingleItem() in Activity:

private fun updateSingleItem(index: Int) {
    Log.e("updateSingleItem", "$newSelectedPosition")
    card_view.setBackgroundColor(resources.getColor(R.color.green))
    adapter.notifyItemChanged(index)
}

setOnClickListener from adapter interface to record the selected item's index in Activity:

        adapter.setOnClickListener(object :
        DailyReportsAdapter.OnClickListener {
        override fun onClick(position: Int, user: User) {
            newSelectedPosition = position
            Log.e("tag", "$newSelectedPosition")

            et_daily_report_employee.text = getString(
                R.string.tv_name,
                user.firstName,
                user.lastName
            )
        }
    })

Adapter class:

open class DailyReportsAdapter(
private val context: Context,
private var list: ArrayList<User>

) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

// A global variable for OnClickListener interface.
private var onClickListener: OnClickListener? = null
private var selectedItemPosition: Int = 0

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return MyViewHolder(
        LayoutInflater.from(context).inflate(
            R.layout.item_daily_report_layout,
            parent,
            false
        )
    )
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, @SuppressLint("RecyclerView") position: Int) {
    val model = list[position]
    if (holder is MyViewHolder) {
        holder.itemView.employee_name.text = "${model.firstName} ${model.lastName}"
      j

// Assign the on click event for item view and pass the required params in the on click function. holder.itemView.setOnClickListener { if (onClickListener != null) { onClickListener!!.onClick(position, model) } } } }

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

fun setOnClickListener(onClickListener: OnClickListener) {
    this.onClickListener = onClickListener
}

/**
 * An interface for onclick items.
 */
interface OnClickListener {
    fun onClick(position: Int, user: User)
}

class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)

}

CodePudding user response:

You need to separate out the Adapter (a thing which takes data and controls how it's displayed) from the rest of the UI (e.g. the Activity hosting it). If there's a button in your UI that means "update a thing in the adapter", really all the Activity or Fragment should be doing is telling the adapter "hey, here's a thing, do what you need to do"

It's not a requirement, but separating the concerns like this means the adapter can be self-contained, and the things that interact with the adapter don't need to know how it works, don't need to poke around at it internally (e.g. calling notifyItemChanged) or anything like that. By declaring visible (non-private) methods on the adapter, you can define its interface, how other components are meant to interact with it. Then you can handle all the logic of how that's supposed to work inside the method and the adapter itself


So I don't know how your app works, but this is just an example so you can work out a way to adapt it for what you actually need. I'm gonna imagine you have a list, with items you can select by tapping on them, but there's a button in the activity you need to press to actually highlight the last one tapped

In the Activity, onCreate or whatever

button.setOnClickListener {
    adapter.highlightLastTapped()
}

In the Adapter

// making this nullable for a "nothing selected" value, and it's easy to work with
// using Kotlin's null-checking features
private var lastTappedPosition: Int? = null

// check this in ``onBindViewHolder`` to see if that item should be highlighted
private var currentlyHighlightedPosition: Int? = null

// internal function you can call from a click listener on an item
private fun setLastTappedPosition(position: Int) {
    lastTappedPosition = position

    // If something was previously highlighted, we can update it right here.
    // This is an example of the kind of logic you can do if you put the adapter
    // in charge of its own state, instead of calling notifyItemChanged etc from outside
    val previousHighlight = currentlyHighlightedPosition
    currentlyHighlightedPosition = null
    previousHighlight?.let { 
        notifyItemChanged(it)
    }
}

// externally visible function - this is just "highlight whatever you need to"
// but you could pass in a parameter if you want. Keep the logic in the adapter though!
fun highlightLastTapped() {
    lastTappedPosition?.let {
        currentlyHighlightedPosition = it
        notifyItemChanged(it)
    }
}

That's just a basic sketch of what you could do, but I hope you get the idea. Treat the adapter as its own, independent class that manages its own state and logic, and let other components talk to it in a limited way, giving it just the data and events it needs

CodePudding user response:

Here is my solution. I still need to persist the selected items with sharedPrefs, but it works as is! Thanks @cactustictacs

My Adapter class:

open class DailyReportsAdapter(private val context: Context, private var list: ArrayList<User>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private var onClickListener: OnClickListener? = null
private var selectedItemPosition: Int? = null

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return MyViewHolder(
        LayoutInflater.from(context).inflate(
            R.layout.item_daily_report_layout,
            parent,
            false
        )
    )
}


override fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    @SuppressLint("RecyclerView") position: Int
) {
    val model = list[position]
    if (holder is MyViewHolder) {
        holder.itemView.employee_name.text = "${model.firstName} ${model.lastName}"


        if (selectedItemPosition == position) {
            holder.itemView.card_view.setBackgroundColor(Color.parseColor("#8BC34A"))

        } else {
            holder.itemView.card_view.setBackgroundColor(Color.parseColor("#ffffff"))

        }


        holder.itemView.setOnClickListener {
            if (onClickListener != null) {
                onClickListener!!.onClick(position, model)
            }
        }
    }
}

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

/**
 * A function for OnClickListener where the Interface is the expected parameter and assigned to the global variable.
 *
 * @param onClickListener
 */
fun setOnClickListener(onClickListener: OnClickListener) {
    this.onClickListener = onClickListener
}

/**
 * An interface for onclick items.
 */
interface OnClickListener {

    // Define a function to get the required params when user clicks on the item view in the interface.
    fun onClick(position: Int, user: User)
}

fun updateSelected(index: Int): Int {
    Log.e("adapter", "updateSelected() executed")
    selectedItemPosition = index
    return selectedItemPosition as Int
}

/**
 * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
 */
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)

}

And in my Activity I used the following code:

Global variable:

var selectedItemPositionFromActivity: Int = 0

Button Click in Activity:

            R.id.btn_add_daily_report_submit -> {
                Log.e("btnclick", "$selectedItemPositionFromActivity")
                if (validateDailyReportDetails()) {
                    uploadDailyReportDetails()
                adapter.updateSelected(selectedItemPositionFromActivity)
                    adapter.notifyItemChanged(selectedItemPositionFromActivity)
                    
                }
            }

ItemClick from interface in adapter where I record the position of the selected item. This is also in Activity:

//Define the onclick listener here that is defined in the adapter class and handle the click on an item in the base class.
    adapter.setOnClickListener(object :
        DailyReportsAdapter.OnClickListener {
        override fun onClick(position: Int, user: User) {
            selectedItemPositionFromActivity = position
        }
    })
  • Related