2

I have a RecyclerView which allows swipe-to-delete functionality. After deliting, a Snackbar shows to confirm deletion with an action that allows users to "undo" the delete.

Everything works fine until I delete the item at position 0 then hit undo. The item will be reinserted back into the list but users will need to scroll up to bring it back into view.

What I have tried

  • Setting recyclerView.isNestedScrollingEnabled" on the RecyclerView

  • Using layoutCoordinator.scrollTo(0, 0) on the Coordinator Layout

  • Using recyclerView.smoothScrollToPosition(0) on the RecyclerView

  • Using recyclerView.scrollToPosition(0) on the RecyclerView

  • Using recyclerView.scrollTo(0, 0) on the RecyclerView

  • Using itemAdapter.notifiyItemInserted(0) on the Adapter

  • Using itemAdapter.notifiyItemchanged(0) on the Adapter

  • Using itemAdapter.notifyDataSetChanged() on the Adapter

  • Using layoutManager.scrollToPositionWithOffset(0, 0) on the LayoutManager

  • Creating a custom LayoutManager and overriding smoothScrollToPosotion() with my own implementation.

None of the above have offered a solution.


Below are the workings.

ItemsFragment

Inside onCreateView - here is setting up the itemAdapter and recyclerView:

    val itemAdapter = object : ItemRecyclerViewAdapter() {
        override fun onItemClicked(item: Item) {
            // todo
        }
    }

    val layoutManager = LinearLayoutManager(context)
    recyclerView.layoutManager = layoutManager
    recyclerView.adapter = itemAdapter

Here is my swipeToDeleteCallback with Snackbar action to "undo" the delete:

val swipeToDeleteCallback = object : SwipeToDeleteCallback() {
        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            val position = viewHolder.adapterPosition
            val item = itemAdapter.currentList[position]
            viewModel.deleteItem(item)

            val snackBar = Snackbar.make(
                recyclerView,
                getString(R.string.snackbar_msg_deleted_card),
                Snackbar.LENGTH_LONG
            )
            snackBar.setAction(R.string.snack_bar_undo) {
                viewModel.restoreItem(item)
                recyclerView.smoothScrollToPosition(position)
            }
            snackBar.show()
        }
    }
val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)

Setting the items which are LiveData observed from the ViewModel. Changes are immediately passed to the adapter:

val items = viewModel.items.await()
    items.observe(viewLifecycleOwner, Observer {
        it?.let {
            itemAdapter.setItems(it)
        }
    })

ItemsAdapter

Submitting the list with DiffUtilCallback:

fun setItems(list: List<Item>?) {
    adapterScope.launch {
        val itemsList = when(list) {
            null -> emptyList()
            else -> list.sortedByDescending {
                it.itemId
            }
        }
        withContext(Dispatchers.Main) {
            submitList(itemsList)
        }
    }
}

Temporary fix

So far the only thing that has worked is this hacky solution inside my Snackbar action:

snackBar.setAction(R.string.snack_bar_undo) {
                viewModel.restoreItem(item)
                if (position == 0) {
                    itemsAdapter.notifyItemInserted(0)
                    recyclerView.smoothScrollToPosition(0)
                }
            }

Here I'm checking if the item position is 0. If so then telling the adapter that there's a new item inserted at position 0 - then initiating scrolling. Otherwise, don't do anything because items at any position other than 0 will insert and animate fine beceause of the DiffUtilCallback.

This temporary fix works but the scrolling is "snappy" and produces an error in the logs:

RecyclerView: Passed over target position while smooth scrolling

Also, this solution does not work 100% of the time. It's more like 60% of the time.

Does anybody know of a better solution/something I am missing and a way to resolve the error above?

Muzzle
  • 233
  • 2
  • 11

0 Answers0