I have a RecyclerView in my app that contains messages. On swiping right, I've configured it to delete the messages and on swiping left, to archive the messages. Here's the code for the ItemTouchHelper part related to deleting and archiving:
if (direction == ItemTouchHelper.LEFT) {
donorMessagesAdapter.deleteMessages(position);
} else if (direction == ItemTouchHelper.RIGHT) {
donorMessagesAdapter.archiveMessages(position);
}
Here are the deleteMessages() and archiveMessages() functions (they're almost the same):
public void deleteMessages(final int position) {
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
}
}
});
snackbar.show();
}
public void archiveMessages(final int position) {
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
}
}
});
snackbar.show();
}
Everything works perfectly well when deleting/archiving one message. Swiping deletes or archives the message after the Snackbar disappears, and when I click undo, the deletion/archival is reversed. Problems arise when deleting/archiving multiple messages.
Problem 1: When I delete/archive multiple messages without waiting for the Snackbar to disappear for each message, all except the first message is deleted/archived. What it makes it stranger for me is that it's not that only the last message is deleted/archived, all except the first are deleted/archived.
Problem 2: When I delete/archive multiple messages without waiting for the Snackbar to disappear for each message, if I press UNDO, that is, on the Snackbar for the last message that was deleted/archived, the app crashes.
By the way,I'm using Room Library to store the messages in a database (referenced by MainActivity.database).
I think the problem is in the addCallback() function for the Snackbar. I'm supposed to do something differently for event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE
from event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT
, but I can't figure out exactly what I'm supposed to do differently, and what exactly causes the problem.
Here is my entire adapter class for reference (error message lines marked with comment next to them):
package com.example.treeapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.mikhaellopez.circularimageview.CircularImageView;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class DonorMessagesAdapter extends RecyclerView.Adapter<DonorMessagesAdapter.DonorMessagesViewHolder> { // line 24 in error message
private Activity activity;
public String email;
public List<String> sortedPeers;
public static CircularImageView imageView;
private String recentlyDeletedPeer;
private int recentlyDeletedPeerPosition;
private String recentlyArchivedPeer;
private int recentlyArchivedPeerPosition;
public static class DonorMessagesViewHolder extends RecyclerView.ViewHolder {
public Button middleButton;
public TextView nameTextView;
public TextView messageTextView;
DonorMessagesViewHolder(View view) {
super(view);
middleButton = view.findViewById(R.id.middle_button);
imageView = view.findViewById(R.id.image_view);
nameTextView = view.findViewById(R.id.text_view_name);
messageTextView = view.findViewById(R.id.text_view_message);
}
}
DonorMessagesAdapter(Activity activity, String email) {
this.activity = activity;
this.email = email;
this.sortedPeers = getSortedPeers(email);
}
@NonNull
@Override
public DonorMessagesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message, parent, false);
return new DonorMessagesViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull DonorMessagesViewHolder holder, int position) {
final String current = sortedPeers.get(position);
// Start of getting last message
String lastMessage;
String lastMessageType;
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, current, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, current, 0);
if (sentMessages.size() == 0) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
} else if (receivedMessages.size() == 0) {
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1) {
lastMessageType = "sentImage";
}
} else {
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime)) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1));
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
} else {
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1) {
lastMessageType = "sentImage";
}
}
}
// End of getting last message
if (MainActivity.database.plantersDao().checkEmail(current) == 1) {
if (MainActivity.database.plantersDao().getImageUrl(current) != null) {
new DownloadImageTask(imageView).execute(MainActivity.database.plantersDao().getImageUrl(current));
}
} else if (MainActivity.database.donorsDao().checkEmail(current) == 1) {
if (MainActivity.database.donorsDao().getImageUrl(current) != null) {
new DownloadImageTask(imageView).execute(MainActivity.database.donorsDao().getImageUrl(current));
}
}
if (MainActivity.database.plantersDao().checkEmail(current) == 1) {
holder.nameTextView.setText(MainActivity.database.plantersDao().getName(current));
} else if (MainActivity.database.donorsDao().checkEmail(current) == 1) {
holder.nameTextView.setText(MainActivity.database.donorsDao().getName(current));
}
if (lastMessageType.equals("received")) {
if (lastMessage.length() > 17) {
holder.messageTextView.setText(lastMessage.substring(0, 16) + "...");
} else {
holder.messageTextView.setText(lastMessage);
}
} else if (lastMessageType.equals("sent")) {
if (lastMessage.length() > 12) {
holder.messageTextView.setText("You: " + lastMessage.substring(0, 11) + "...");
} else {
holder.messageTextView.setText("You: " + lastMessage);
}
} else if (lastMessageType.equals("receivedImage")) {
holder.messageTextView.setText("Image");
} else if (lastMessageType.equals("sentImage")) {
holder.messageTextView.setText("You: " + "Image");
}
holder.middleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(view.getContext(), MessageActivity.class);
intent.putExtra("email", email);
intent.putExtra("peerEmail", current);
intent.putExtra("archived", 0);
view.getContext().startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return sortedPeers.size();
}
public void reload() {
sortedPeers = getSortedPeers(email);
notifyDataSetChanged();
}
private List<String> getSortedPeers(String email) {
List<String> senders = MainActivity.database.receivedMessagesDao().getSendersForReceiver(email, 0);
List<String> receivers = MainActivity.database.sentMessagesDao().getReceiversForSender(email, 0);
List<String> peers = new ArrayList<>();
peers.addAll(senders);
peers.addAll(receivers);
peers = new ArrayList<>(new HashSet<>(peers));
List<String> sortedPeers = new ArrayList<>();
while (peers.size() > 0) {
LocalDateTime latestMessageTime = null;
String latestPeer = null;
for (String peer : peers) {
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, peer, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, peer, 0);
LocalDateTime lastMessageTime;
if (sentMessages.size() == 0) {
lastMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
} else if (receivedMessages.size() == 0) {
lastMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
} else {
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime)) {
lastMessageTime = lastReceivedMessageTime;
} else {
lastMessageTime = lastSentMessageTime;
}
}
if (latestMessageTime != null) {
if (lastMessageTime.isAfter(latestMessageTime)) {
latestMessageTime = lastMessageTime;
latestPeer = peer;
}
} else {
latestMessageTime = lastMessageTime;
latestPeer = peer;
}
}
sortedPeers.add(latestPeer);
peers.remove(latestPeer);
}
return sortedPeers;
}
public void deleteMessages(final int position) {
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
}
}
});
snackbar.show();
}
public void archiveMessages(final int position) {
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
reload();
}
}
});
snackbar.show();
}
}
And here is the error I get (my classes marked with **):
2020-09-12 19:24:11.547 689-689/com.example.treeapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.treeapp, PID: 689
java.lang.ArrayIndexOutOfBoundsException: length=0; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
**at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:74)
at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:24)**
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1762)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:955)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3092)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2779)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1863)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8072)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:723)
at android.view.Choreographer.doFrame(Choreographer.java:658)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
from Configure ItemTouchHelper in RecyclerView to work for Snackbar being dismissed by another Snackbar being displayed
No comments:
Post a Comment