Reputation: 17
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
Upvotes: 0
Views: 503
Reputation: 19622
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 11bb
s. 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!
edit If you need arbitrary matching indices (other than first or last) you'll need to code that logic - it's probably best to put it in a function:
fun <T> Iterable<T>.matchingIndexNumber(ordinal: Int, predicate: (T) -> Boolean) =
mapIndexedNotNull { i, item -> if (predicate(item)) i else null }
.getOrNull(ordinal - 1) ?: -1
Now you can call items.matchingIndexNumber(2, ::matchesRule)
to get the 2nd match's index, etc.
By the way, I kinda glossed over this, but the reason I used indices was because they're guaranteed to be unique - if you know all your items will be unique objects (i.e. none of them return true for equals
when compared to the other items) then you can drop the indices entirely, and just store the matching object:
// potentially store the important -item- itself
private val trueItem: String? = null
fun setData(newData: List<String>) {
...
// just set the first item that matches, or null if nothing matches
trueItem = items.first(::matchesRule)
...
}
override fun onBindViewHolder(holder: TimeViewHolder, position: Int) {
...
when(trueItem) {
null -> // no important item in list - display this normally
item -> // this item is the important one
else -> // there's an important item, this isn't it
}
...
}
Which is a whole lot simpler! The indices approach works no matter what, but if your items are all unique, this is the way I'd go. (If they're not, e.g. you have two "11aa"
strings, they'll both match trueItem
in that when
block so they'll both show true or whatever)
Anyway, if you're working with the items themselves instead of having to juggle indices and return -1 for missing stuff, you can just do this:
// get the 3rd matching item
trueItem = items.filter(::matchesRule).getOrNull(3)
// or
trueItem = items.filter(::matchesRule).drop(2).firstOrNull()
// or if you want the 3rd item, but if there isn't one you want the 2nd, and so on
trueItem = items.filter(::matchesRule).take(3).lastOrNull()
There are way more options in the standard library for working with the collection of items directly, it's much more flexible, so I'd recommend it if you can!
(You could also do the take(count).lastOrNull()
trick in that function I wrote if you do want/need to stick with the indices though)
Upvotes: 1