1

I'm trying to create a horizontal recyclerview with a single selection. The code below works but if you scroll, then 2 or more objects can be selected, even though I specify the single selection. I guess it's because the onBindViewHolder is not called if the item is off the screen but when I call the notifyDataSetChanged the behavior is still the same. The callbacks are correct, but the selector background, shows behind multiple objects.

class CategoryAdapter(private val listener: CategoryListener) :
ListAdapter<CategoryListing, CategoryAdapter.ViewHolder>(CategoryListing.DIFF_COMPARATOR) {

init {
   setHasStableIds(true)
}

var tracker: SelectionTracker<Long>? = null

...

override fun getItemId(position: Int): Long = position.toLong()

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    tracker?.let {
        if (!it.hasSelection() && position == 0) {
            it.select(0)
        }
    }
    holder.bind(
        currentList[position],
        listener,
        tracker?.isSelected(position.toLong()) ?: false
    )
}

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    fun bind(
        category: CategoryListing,
        listener: CategoryListener,
        isSelected: Boolean
    ) {
        if (isSelected) selector.show() else selector.hide()

        itemView.setOnClickListener {
            tracker?.clearSelection()                       // this doesn't seem to do anything
            // notifyDataSetChanged()                       // neither this
            // tracker?.select(adapterPosition.toLong())    // or this
            listener.onItemClicked(category, adapterPosition)
        }
    }

    fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
        object : ItemDetailsLookup.ItemDetails<Long>() {
            override fun getPosition() = adapterPosition
            override fun getSelectionKey() = itemId
        }
}

Fragment:

rv_categories.adapter = categoryAdapter
        rv_categories.setHasFixedSize(true)
        selectionTracker = SelectionTracker.Builder<Long>(
            CardsFragment::javaClass.name,
            rv_categories,
            CategoryItemKeyProvider(rv_categories),
            CategoryDetailsLookup(rv_categories),
            StorageStrategy.createLongStorage()
        ).withSelectionPredicate(SelectionPredicates.createSelectSingleAnything()).build()
        categoryAdapter.tracker = selectionTracker
        selectionObserver = CategorySelectionObserver(selectionTracker) { selectedPosition ->
            onSelectionChanged(selectedPosition)
        }
        selectionTracker.addObserver(selectionObserver)

...
override fun onItemClicked(category: CardCategoryListing, position: Int) {
    // load the data for that category
}

private fun onSelectionChanged(selectedItemPosition: Long) {
  rv_categories.findViewHolderForAdapterPosition(selectedItemPosition.toInt())?.itemView?.performClick()

}

Can anyone help?

Eliza Camber
  • 1,536
  • 1
  • 13
  • 21
  • I don't know enough about the specific APIs nor Kotlin, but, three things that might be useful to look into: 1: In `onSelectionChanged`, you call `performClick`. 2: The commented-out method calls have side-effects, so the ordering of them matters. 3: You call `select` directly `onBindViewHolder`, not merely in a callback. – MelvinWM May 20 '20 at 06:21
  • If I comment out the onclick from the `onSelectionChanged` I still get the same behavior. The same also when I change the order of the commented out calls (of I completely comment them out all of them besides the `listener.onItemClicked(category, adapterPosition)`) – Eliza Camber May 20 '20 at 09:27
  • What if you comment out: ```tracker?.let { if (!it.hasSelection() && position == 0) { it.select(0) } }``` ? – MelvinWM May 20 '20 at 11:16
  • tried that as well :( – Eliza Camber May 21 '20 at 08:05
  • I would guess that there are two parts reg. selection, one part that holds selection state (`tracker: SelectionTracker`) and one part that shows whether something is selected or not (`selector.show()`/`selector.hide()`). If so, I would guess that one approach is to design the system to keep them in sync, such that an item is selected if and only if the selector shows for that part. – MelvinWM May 21 '20 at 09:53
  • Another aspect is then what is selected. Ensure you understand when something is selected and not (possibly reading through APIs, as well as comment out all selection code, run and comment it in bit by bit, as well as debug-printing and other methods (some people like debug state inspection)). Then I would guess that deselecting everything and selecting it again (as you already do) in order to make one item being selected would work. – MelvinWM May 21 '20 at 09:57
  • There's also a similar question and some answers here which you may find useful: https://stackoverflow.com/questions/27194044/how-to-properly-highlight-selected-item-on-recyclerview . – MelvinWM May 21 '20 at 10:02
  • 1
    thanks @MelvinWM. The selector is just the view, I should have written that. The selection is done correctly, the view is the one that doesn't update properly. Thanks for the above link. I saw it too but none of the answers is using the recyclerview-selector. I think I'll change to a manual solution as well – Eliza Camber May 22 '20 at 07:43
  • 1
    @DilankaLaksiri I ended up ditching the tracker for my project and implementing the logic manually. – Eliza Camber Mar 29 '21 at 09:19

0 Answers0