0

I have a RecyclerView and have an undo-functionality, where you delete an item but can press undo on the appearing Snackbar to insert the item back into the list.

Now unfortunately, this happens:

When deleting elements quickly in a row, some of them are being restored while deleting another. It seems like creating a second Snackbar too quickly after the first triggers the Action, e.g. in this case the undo.

Could this be the case, or is it my code that is faulty?


The code isn't too complex:

   private void deleteSATemp(int position) {
        SelfAffirmation saToDelete = selfAffirmations.get(position);
        String content = saToDelete.getContent();
        String partOfContent = content.substring(0, 20);

        selfAffirmations.remove(position);
        notifyItemRemoved(position);

        createUndoSnackbar(saToDelete, position, partOfContent);
    }

This is the "fake" removal part, where the element is removed from the list visually but not from the database yet, in case of an undo action.

And here is the Snackbar part:

   private void createUndoSnackbar(SelfAffirmation saToDelete, int position, String pieceOfSAContent) {
        Snackbar snackbar = Snackbar.make(((Activity) mContext).findViewById(R.id.sa_activity_parent_layout), mContext.getString(R.string.snackbarItemDeletedPlaceholder, pieceOfSAContent),
                5000);

        snackbar.setAction(R.string.snackbarUndoText, view -> {
            selfAffirmations.add(position, saToDelete);
            notifyItemInserted(position);
        });
        snackbar.addCallback(new Snackbar.Callback() {
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
                    mCallbackListener.onItemDeleted(saToDelete);
                }
            }
        });
        snackbar.show();
    }

The database works with LiveData. I can't imagine that this Android feature would be implemented so carelessly, so it has to be my code.

Lino
  • 5,084
  • 3
  • 21
  • 39
Big_Chair
  • 2,781
  • 3
  • 31
  • 58

1 Answers1

2

Yes, IMO you're right in your supposition of events popping up too fast.

According to Snackbar's Documentation you can only have one snackbar displayed at a time.

Snackbars appear above all other elements on screen and only one can be displayed at a time. They automatically disappear after a timeout or after user interaction elsewhere on the screen, particularly after interactions that summon a new surface or activity. Snackbars can be swiped off screen.

When another snackbar appears the first one is automatically dismissed.

In the other hand: please review how you are implementing this selfAffirmations.remove(position); call. If you are removing positions in an array, and two calls to this method arise at about the same time this can produce unexpected behavior. You'd better search and delete by something unique and non-transferable (the current position of all items will change if you delete the first one), like an id. Cheers.

Ictus
  • 1,435
  • 11
  • 20
  • Your second point made me realize that it may actually be caused by the `LiveData` functionality. When one item is deleted, it calls the observer, which in turn calls `notifyDataSetChanged()`. So while the second deleted item is fake-deleted but held behind the curtains, the dataset updates and thus shows it in the list again. And if I wait for the second `Snackbar` to disappear, it is actually deleted as well. I will have to test this and report back later. – Big_Chair Dec 31 '19 at 18:59
  • Good! Glad to help. I have no enough data to give a whole answer for your use case, that's why my general approach, but I know concurrency is a complex topic to address. And some data structure implementations are prone to fail when calls are arriving in the wrong order, as you realized it could be. – Ictus Dec 31 '19 at 19:36
  • [@Big_Chair Did](https://stackoverflow.com/users/1972372/big-chair) did it work? – Ictus Jan 06 '20 at 17:07