Home > OS >  RecyclerView - change item value only if item satisfies a condition?
RecyclerView - change item value only if item satisfies a condition?

Time:09-10

I am having an issue with RecyclerView - in onBindViewHolder(), when I make a condition for each item in the list (going through the whole list, position by position), and want to change the item text value, it doesn't work. I assume that's because RecyclerView recycles items. `

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

    val time = holder.itemView.findViewById<TextView>(R.id.timeTextView)

    val item = list[position]

    //items in list: "11aa", "11bb", "11cc"
    if (item.value.substring(0, 2).toInt() == 11 && item.value.substring(2,4) == "bb") {
       time.text = "true"
     } else {
       time.text="false"
     } 
    // list should be shown as: "false", "true", "false"


    if (item.value.substring(0,2).toInt() == 11 && item.value.substring(2,4) == "aa") {
      time.text="true"
    } else {
       time.text="false"
    }
      //list should be shown as: "true", "false", "false"
`

Example: item is "11bb" -> show true, all other items should be false (including "11aa")
         item is "11aa" -> show true, all other items are false (including "11bb")

I would greatly appreciate any suggestion!

I tried the else condition, so it is not only "if" without other possible outcome, still doesn't work

CodePudding user response:

For every item in the list, you're doing two checks. First you check if item is "11bb" and display true or false. But then you throw that result out the window by checking if it's "11aa" instead, and display true or false based on that.

The end result is each item displays true if it's "11aa", otherwise it shows false.


Here's what you said you wanted in your question:

Example: item is "11bb" -> show true, all other items should be false (including "11aa")
         item is "11aa" -> show true, all other items are false (including "11bb")

So it sounds like you want a single item to display true, and displaying that sets the others to false, right?

Those two rules you've provided contradict each other - what if you have both "11aa" and "11bb" in your data, like your example does?

//items in list: "11aa", "11bb", "11cc"

Which one should show true? The first one in the list, "11aa"? Or does a later one override the earlier one, so "11bb"? What's the rule for resolving this?


Here's a way you can do it, assuming you want the first one in the list.

First, you need a function to set your adapter's data. This is so you can check through it, and work out in advance which item needs to be set to true:

// inside your adapter

// your internal data - private so anything setting the data -has to- go through
// the setter function below. 
private var items: List<String>

// index of the item that should be displayed as true, or null if there isn't one
// (I'm storing an index instead of the item in case there are duplicates in the list)
private val trueItemPosition: Int? = null

fun setData(newData: List<String>) { 
    // set the new data
    items = newData
    // store the index of the first item that matches your rules, or null if none do
    val matchedIndex = items.indexOfFirst(::matchesRule)
    trueItemPosition = if (matchedIndex == -1) null else matchedIndex
    
    // always refresh after setting data!
    notifyDataSetChanged()
}

// you don't need to do any of that Int conversion or substring stuff, just match the string!
private fun matchesRule(item: String) = when {
    item.startsWith("11aa") -> true
    item.startsWith("11bb") -> true
    else -> false
}

Then in onBindViewHolder you can decide what to do depending on the current item's position and the value of trueItemPosition:

// in onBindViewHolder

when(trueItemPosition) {
    null -> {
        // display your item normally - there's no 11aa or 11bb in the list
    }
    position -> {
        // this item is the matching one
        time.text = "true"
    }
    else -> {
        // there's a matching item, but this isn't it
        time.text = "false"
    }
}

And that's pretty much it! You can use indexOfLast if you want the last matching item to take precedence, and you could make separate functions for the different rule matchers if you want, say, a 11aa anywhere in the list to take precedence over any 11bbs. Just run them one after another on the whole data set in order of preference, until you get a matching index.


Like I said, this relies on you setting all your data through that setData function, so it can work things out before you display anything. That includes the initial data you provide - so if you're passing data in as a constructor parameter, that can't be a property and you need to call setData with that parameter in an init block:

class MyAdapter(
    ...
    data: List<String> // not a val/var, we're just using this temporarily
    ... {

    init {
        // set the internal data through the setter
        setData(data)
    }


}

and that should do it!

  • Related