Paging v3 does not work with recyclerview-selection out-of-the box because selection library does use stable ids, but paging v3 does not support them.
To make them work with each other you need to implement your own stable id key provider. It holds item ids once they bound to recycler view.
class MyStableIdKeyProvider(
private val mRecyclerView: RecyclerView
) : ItemKeyProvider<Long>(SCOPE_CACHED) {
init {
mRecyclerView.addOnChildAttachStateChangeListener(
object : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {
onAttached(view)
}
override fun onChildViewDetachedFromWindow(view: View) {
onDetached(view)
}
}
)
}
private val mPositionToKey = mutableMapOf<Int, Long>()
private val mKeyToPosition = mutableMapOf<Long, Int>()
fun /* synthetic access */onAttached(view: View) {
val holder = mRecyclerView.findContainingViewHolder(view)
if (holder == null) {
Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onAttached event.")
return
}
val position = holder.bindingAdapterPosition
val myItemId = (holder as ItemVH).myItemId
if (position != RecyclerView.NO_POSITION && myItemId != null) {
mPositionToKey[position] = myItemId
mKeyToPosition[myItemId] = position
}
}
fun /* synthetic access */onDetached(view: View) {
val holder = mRecyclerView.findContainingViewHolder(view)
if (holder == null) {
Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onDetached event.")
return
}
val position = holder.bindingAdapterPosition
val myItemId = (holder as ItemVH).myItemId
if (position != RecyclerView.NO_POSITION && myItemId != null) {
mPositionToKey.remove(position)
mKeyToPosition.remove(myItemId)
}
}
override fun getKey(position: Int): Long? = mPositionToKey[position]
override fun getPosition(key: Long): Int = mKeyToPosition[key] ?: RecyclerView.NO_POSITION
companion object {
private const val TAG = "MyKeyProvider"
}
}
Your ItemVH has to provide its id. You can't use getItemId() because it always returns NO_ID.
class ItemVH(
view: View,
selectionTracker: SelectionTracker<Long>,
) : RecyclerView.ViewHolder(view) {
protected var myItem: MyItem? = null
val myItemId: Long? get() = myItem?.id
fun bind(myItem: MyItem, position: Int) {
this.myItem = myItem
// Put data to item's views
// ..
// Update checkmark visibility passing `myItemId` to the selection tracker
if (selectionTracker.isSelected(myItemId)) {
selectedView.visibility = View.VISIBLE
} else {
selectedView.visibility = View.GONE
}
}
fun getItemDetails() = object: ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition() = bindingAdapterPosition
override fun getSelectionKey() = myItemId
}
fun clear() {
myItem = null
}
}
Adapter is quite simple
class MyItemListAdapter : PagingDataAdapter<MyItem, ItemVH>(DIFF_CALLBACK) {
lateinit var selectionTracker: SelectionTracker<Long>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemVH {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return ItemVH(view, selectionTracker)
}
override fun onBindViewHolder(viewHolder: ItemVH, position: Int) {
val myItem = getItem(position)
if (myItem == null) {
viewHolder.clear()
} else {
viewHolder.bind(myItem, position)
}
}
companion object {
private val DIFF_CALLBACK = object :
DiffUtil.ItemCallback<MyItem>() {
// ...
}
}
}
Item details lookup is required to build selection tracker:
class ItemListLookup(
private val rv: RecyclerView
) : ItemDetailsLookup<Long>() {
override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? {
val view = rv.findChildViewUnder(e.x, e.y)
if(view != null) {
return (rv.getChildViewHolder(view) as ItemVH)
.getItemDetails()
}
return null
}
}
Fragment ties all together
class MyFragment : Fragment() {
private val adapter = MyItemListAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.recyclerView.adapter = adapter
adapter.selectionTracker = SelectionTracker.Builder<Long>(
"item_selection",
view.recyclerView,
MyStableIdKeyProvider(view.recyclerView),
ItemListLookup(view.recyclerView),
StorageStrategy.createLongStorage()
).build()
adapter.selectionTracker.addObserver(object : SelectionTracker.SelectionObserver<Long>() {
override fun onItemStateChanged(key: Long, selected: Boolean) {
// handle selection change event
}
override fun onSelectionCleared() {
// clear selection
}
})
// ...
}
}