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"
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