We have this feature in our App where we can drag recycler view Items up and down.
Inside onMove() of ItemTouchHelper.Callback() we call
adapter.onItemMove(source.adapterPosition, target.adapterPosition)
and the adapter code goes like this
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
Collections.swap(dataList, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
Now, earlier adapter class was extending RecyclerView.Adapter() and we have following method to update our list using DiffUtils
fun setDataList(feeds: List<User>): DiffUtil.DiffResult {
val diffResult = DiffUtil.calculateDiff(ContentDiffCall(mFeeds, feeds))
this.mFeeds.clear()
this.mFeeds.addAll(feeds)
return diffResult
}
Drag and drop were working fine with this.
But when we have extended our adapter class from ListAdapter, the already working functionality(drag and drop) is breaking. The first item in the recycler view is not dragging beyond the second item and also position item dragged is not getting updated.
Reverting ListAdapter implementation makes it work again.
Couldn't understand why is this not working when ListAdapter itself extends RecyclerView.Adapter.
Adapter class
class ContentListAdapter(
private val headerListener: HeaderClickListener?,
private val listener: ListItemClickListener?,
private val screen: Screen,
private val feedInteractor: FeedInteractionManager
) : ListAdapter<BaseUiModel, RecyclerView.ViewHolder>(ContentListDiffCall()), ContentTouchHelperAdapter {
private var dataList = ArrayList<BaseUiModel>()
init {
setHasStableIds(true)
}
fun setDataList(data: List<BaseUiModel>) {
dataList.clear()
dataList.addAll(data)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
HFType.CONTENT_LIST_HEADER.ordinal -> ContentListHeaderViewHolder.getInstance(
parent,
headerListener,
R.layout.layout_content_list_header,
screen
)
HFType.SONG.ordinal -> SongViewHolder.getInstance(parent, listener)
else -> throw RuntimeException("there is no type that matches the type $viewType ; make sure your using types correctly")
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position).hfType.ordinal
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = getItem(position)
when (holder) {
is SongViewHolder -> {
if (data is SongUiModel) {
holder.bindViews(data)
}
}
is ContentListHeaderViewHolder -> {
if (data is HeaderUiModel) {
holder.bindViews(data)
}
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
return
}
val data = getItem(position)
when (holder) {
is SongViewHolder -> {
if (data is SongUiModel) {
holder.bindViews(data, payloads)
}
}
is ContentListHeaderViewHolder -> {
if (data is HeaderUiModel) {
holder.bindViews(data, payloads)
}
}
}
}
override fun getItemId(position: Int): Long {
val data = getItem(position)
return data.hashCode().toLong()
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
Collections.swap(dataList, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
super.onViewAttachedToWindow(holder)
if (holder is HomeFeedViewHolder<*>) {
holder.onHolderAttachedInViewPort()
}
}
}
interface ContentTouchHelperAdapter {
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
}
Code snippet inside fragment
private fun setUpRecyclerView() {
rv_item_list.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
recycledViewPool.setMaxRecycledViews(HFType.SONG.ordinal, 12)
recycledViewPool.setMaxRecycledViews(HFType.CONTENT_LIST_HEADER.ordinal, 1)
}
val spaceItemDecorator = ContentListSpaceDecorator(25.dpToPx())
rv_item_list.addItemDecoration(spaceItemDecorator)
adapter = ContentListAdapter(this, this, this, screen, this)
rv_item_list.adapter = adapter
val callback = SimpleTouchListener(adapter, contentListViewModel)
itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper.attachToRecyclerView(rv_item_list)
}
SimpleTouchListener class
class SimpleTouchListener(
private val listAdapter: ContentListAdapter,
private val viewModel: ContentListViewModel
) :
ItemTouchHelper.Callback() {
private var fromPosition: Int? = null
private var toPosition: Int? = null
override fun isLongPressDragEnabled(): Boolean {
return false
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
var dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
if (viewHolder is ContentListHeaderViewHolder) {
dragFlags = 0
}
return makeMovementFlags(dragFlags, 0)
}
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
if (source.itemViewType != target.itemViewType) {
return false
}
if (fromPosition == null) {
fromPosition = source.adapterPosition
}
toPosition = target.adapterPosition
listAdapter.onItemMove(source.adapterPosition, target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, i: Int) {
// Notify the adapter of the dismissal
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
if (viewHolder is ItemTouchHelperViewHolder) {
val itemViewHolder = viewHolder as ItemTouchHelperViewHolder?
itemViewHolder?.onItemSelected()
}
}
super.onSelectedChanged(viewHolder, actionState)
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
if (viewHolder is ItemTouchHelperViewHolder) {
// Tell the view holder it's time to restore the idle state
val itemViewHolder = viewHolder as ItemTouchHelperViewHolder
itemViewHolder.onItemClear()
}
if (fromPosition != null && toPosition != null) {
//This method updates the list ordering in DB
viewModel.onContentPositionChange(fromPosition, toPosition)
}
fromPosition = null
toPosition = null
}
}
we call setAdapterMethod from an observer on Success
private fun setAdapterData(contentList: List<BaseUiModel>?) {
contentList?.let { it ->
adapter.setDataList(it)
adapter.submitList(it)
}
}
ListAdapter
updates its content through submitList
function:
/**
* Submits a new list to be diffed, and displayed.
* <p>
* If a list is already being displayed, a diff will be computed on a background thread, which
* will dispatch Adapter.notifyItem events on the main thread.
*
* @param list The new list to be displayed.
*/
public void submitList(@Nullable List<T> list) {
mDiffer.submitList(list);
}
Then you can just call:
listAdapter.submitList(updatedList)