Search code examples
androidkotlinandroid-recyclerviewsnackbaritemtouchhelper

How to learn whether a Snackbar button was pressed?


I have a Snackbar which is being opened when a user swipes on a RecyclerView element to delete it. And this Snackbar allows a user to undo his action. I know how to get an element of the RecyclerView back. But I also have a database(SQLite). It seems to me, the best way to do a removal from a detabase is to do it when I understand that a user doesn't press "undo". Otherwise I will need to make a removal and then adding.

I want to do something sort of this:

when (snackbar_button){
was_pressed -> adapter.restoreItem(cachedPosition, cachedItem)
was_not_pressed -> dbManager.removeItem(listArray[pos].id.toString())
}

This is my code on MainActivity:

val onSwipe = object : OnSwipe(this) {
    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        val cachedPosition = viewHolder.absoluteAdapterPosition
        val cachedItem = adapter.listArray[cachedPosition]
        when (direction) {
            ItemTouchHelper.RIGHT -> {
                adapter.removeItem(cachedPosition)

Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                        .apply {
                            setAction("Undo") {
                                adapter.restoreItem(cachedPosition, cachedItem)
                            }
                            show()
                        }
                }
            }
        }

    }

My adapter:

fun removeItem(pos: Int) {
    listArray.removeAt(pos)
    notifyItemRemoved(pos)
    }

    fun restoreItem(pos: Int, listMain: ListItem) {
        listArray.add(pos, listMain)
        notifyItemInserted(pos)
  }

My code in DB to delete:

    fun removeItem(_id: String) {
        val id = BaseColumns._ID + "=$_id"

Solution

  • Have a look at the addCallback function - you can add a BaseCallback with an onDismissed function, where you're provided the reason for the dismissal. DISMISS_EVENT_ACTION means your undo button was clicked, anything else means the snackbar was swiped away, disappeared after a timeout, etc.

    So you can do something like this:

    Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
        .setAction("Undo") { // no need to do anything }
        .setCallback(object: BaseCallback<Snackbar> {
            override fun onDismissed(transientBottomBar: Snackbar, event: Int) {
                // if the user didn't click Undo, delete the item
                if (event != DISMISS_EVENT_ACTION) adapter.removeItem(cachedPosition)
            }
        }
        .show()
    

    It would still be a good idea to remove the item from the list though, and restore it in the setAction lambda or in the onDismissed callback if the button was pressed. That way the user sees it disappear and reappear - deferring deletion is good for things like avoiding removing things from databases, where you don't want to touch the real data until you're sure.

    You could still do that with what you have here:

    // delete/hide it
    adapter.removeItem(cachedPosition)
    
    Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
        .setAction("Undo") { // no need to do anything }
        .setCallback(object: BaseCallback<Snackbar> {
            override fun onDismissed(transientBottomBar: Snackbar, event: Int) {
                // add the item back if they DID click Undo
                if (event == DISMISS_EVENT_ACTION) adapter.restoreItem(cachedPosition, cachedItem)
                else // do something else if they didn't, like delete cachedItem from database
            }
        }
        .show()
    
    

    Also if you're going to be accessing the adapter and managing the undo state externally like this, I'd make removeItem return the item that was removed (so you don't have to access the adapter's data list directly) and maybe rename restoreItem to addItem since technically you're inserting whatever you like at a position.

    Really though, it would be better to keep the last deleted item internal to the adapter, so you can call remove(position) and restore() and let the adapter take care of the details and manage its own state. It's just cleaner and prevents bugs