Как я могу отложить (с отменой) смахивание, чтобы удалить Recyclerview, который использует Room и LiveData?

Я реализовал в своем приложении функцию удаления смахивания, используя следующий образец в качестве руководства nemanja-kovacevic / recycler-view-swipe-to-delete. Изначально я использовал простой класс базы данных SQLite, и все работало правильно.

Пытаясь обновить свое приложение, чтобы использовать комнату компонентов архитектуры Android и LiveData, я следовал Комната с видом на Codelab. После обновления кода казалось, что он работает, и работает всего за один раз. Однако, если вы проведете пальцем по другой строке до завершения задержки отмены, LiveData обновит кэшированную копию адаптера списка, так что последующие ожидающие удаления runnables не могут найти элемент, который они должны переместить в списке (позиция = -1), что приводит к сбою приложения.

Это было много объяснений, вот код адаптера:

    public class DropsListAdapter extends RecyclerView.Adapter<DropsListAdapter.DropHolder> {
    private final static int PENDING_REMOVAL_TIMEOUT = 3000; // 3sec
    private final LayoutInflater inflater;
    private Context context;
    private Handler mHandler = new Handler();
    private HashMap<DeadDrop, Runnable> pendingRunnables = new HashMap<>();
    private List<DeadDrop> deadDrops;
    private List<DeadDrop> dropsPendingRemoval;

    DropsListAdapter(Context context) {
        inflater = LayoutInflater.from(context);
        this.context = context;
        this.dropsPendingRemoval = new ArrayList<>();
    }

    @NonNull
    @Override
    public DropHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new DropHolder(inflater.inflate(R.layout.drop_list_item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull DropHolder holder, int position) {
        final DeadDrop deadDrop = deadDrops.get(position);
        if (dropsPendingRemoval.contains(deadDrop)) {
            holder.itemView.setBackgroundColor(Color.WHITE);
            holder.undoIt.setVisibility(View.VISIBLE);
            holder.rowWrapper.setVisibility(View.GONE);
        } else {
            holder.rowWrapper.setVisibility(View.VISIBLE);
            holder.latitude.setText(Converts.latitudeToSexaString(deadDrop.getLatitude()));
            holder.longitude.setText(Converts.longitudeToSexaString(deadDrop.getLongitude()));
            holder.undoIt.setVisibility(View.GONE);
        }
    }

    @Override
    public int getItemCount() {
        if (deadDrops != null)
            return deadDrops.size();
        else return 0;
    }

    void pendingRemoval(int position) {
        final DeadDrop mDeadDrop = deadDrops.get(position);
        if (!dropsPendingRemoval.contains(mDeadDrop)) {
            dropsPendingRemoval.add(mDeadDrop);
            notifyItemChanged(position);
            Runnable pendingRemovalRunnable = new Runnable() {
                @Override
                public void run() {
                    // Here is the problem. After the first item is removed,
                    // the next drop to remove is not found in the newly updated
                    // list of items (deadDrops).
                    int pos = deadDrops.indexOf(mDeadDrop);
                    remove(pos);
                }
            };
            mHandler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
            pendingRunnables.put(mDeadDrop, pendingRemovalRunnable);
        }
    }

    void setDeadDrops(List<DeadDrop> drops) {
        deadDrops = drops;
        notifyDataSetChanged();
    }

    private void remove(int position) {
        DeadDrop drop = deadDrops.get(position);
        dropsPendingRemoval.remove(drop);
        if (deadDrops.contains(drop)) {
            deadDrops.remove(position);
            notifyItemRemoved(position);
            ((DeadDropActivity) context).mDeadDropViewModel.delete(drop);
            notifyDataSetChanged();
        }
    }

    boolean isPendingRemoval(int position) {
        return dropsPendingRemoval.contains(deadDrops.get(position));
    }

    /**
     * Drops List View Holder class
     */
    protected class DropHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        LinearLayout rowWrapper;
        TextView latitude, longitude;
        ImageButton mapIt;
        Button undoIt;

        DropHolder(View itemView) {
            super(itemView);

            rowWrapper = itemView.findViewById(R.id.row_wrapper);
            latitude = itemView.findViewById(R.id.latitude_sexagesimal);
            longitude = itemView.findViewById(R.id.longitude_sexagesimal);
            mapIt = itemView.findViewById(R.id.button_map_it);
            undoIt = itemView.findViewById(R.id.undo_button);
            mapIt.setOnClickListener(this);
            undoIt.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.button_map_it) {

                String gUri = String.format(Locale.ENGLISH,
                        "https://www.google.com/maps/@%f,%f," + DeadDropActivity.GMAPS_CLOSE_ZOOM + "z",
                        deadDrops.get(getLayoutPosition()).getLatitude(),
                        deadDrops.get(getLayoutPosition()).getLongitude());

                Intent gIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(gUri));
                gIntent.setClassName("com.google.android.apps.maps",
                        "com.google.android.maps.MapsActivity");

                try {
                    context.startActivity(gIntent);

                } catch (ActivityNotFoundException ex) {
                    try {
                        String uri = String.format(Locale.ENGLISH, "geo:%f,%f?z=25",
                                deadDrops.get(getLayoutPosition()).getLatitude(),
                                deadDrops.get(getLayoutPosition()).getLongitude());
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
                        context.startActivity(intent);

                    } catch (ActivityNotFoundException innerEx) {
                        Toast.makeText(context, "Please install a maps application or browser.",
                                Toast.LENGTH_LONG).show();
                        innerEx.printStackTrace();
                    }
                }

                Toast.makeText(context, "Map Button clicked at " + getLayoutPosition(), Toast.LENGTH_SHORT).show();
            } else if (v.getId() == R.id.undo_button) {
                DeadDrop deadDrop = deadDrops.get(getLayoutPosition());
                // user wants to undo the removal, let's cancel the pending task
                // Cancelling still works without issue.
                Runnable pendingRemovalRunnable = pendingRunnables.get(deadDrop);
                pendingRunnables.remove(deadDrop);
                if (pendingRemovalRunnable != null)
                    mHandler.removeCallbacks(pendingRemovalRunnable);
                dropsPendingRemoval.remove(deadDrop);
                // this will rebind the row in "normal" state
                notifyItemChanged(deadDrops.indexOf(deadDrop));
                Log.d(TAG, TAG_CLASS + ".onClickUndo(" + getLayoutPosition() + ")");
            }
        }
    }

    /**
     * Utility class
     */
    public static class Converts {

        static String latitudeToSexaString(double latitude) {
            String latDir = (latitude < 0) ? "S" : "N";
            double lat = Math.abs(latitude);
            double s;
            int d, m;

            d = (int) lat;
            m = (int) ((lat - d) * 60);
            s = (((lat - d) * 60) - m) * 60;

            return String.format(Locale.ENGLISH, "%02d\u00B0", d) +
                    String.format(Locale.ENGLISH, "%02d\u0027", m) +
                    String.format(Locale.ENGLISH, "%02.1f\"", s) + latDir;
        }

        static String longitudeToSexaString(double longitude) {
            String lonDir = (longitude < 0) ? "W" : "E";
            double lon = Math.abs(longitude);
            double s;
            int d, m;

            d = (int) lon;
            m = (int) ((lon - d) * 60);
            s = (((lon - d) * 60) - m) * 60;

            return String.format(Locale.ENGLISH, "%02d\u00B0", d) +
                    String.format(Locale.ENGLISH, "%02d\u0027", m) +
                    String.format(Locale.ENGLISH, "%02.1f\"", s) + lonDir;
        }
    }
}

Вот logcat, где он показывает, что indexof возвращает -1, потому что экземпляр не найден, но это потому, что это новый экземпляр того же объекта (ID тот же, но object.toString () другой :

2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 2
2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {}
2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|16
2019-07-14 02:29:20.896 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): 2
    DropID = com.daweber.deaddrop.DeadDrop@d769128(ID|16)
    DropsList = [com.daweber.deaddrop.DeadDrop@50eec1a, com.daweber.deaddrop.DeadDrop@966a34b, com.daweber.deaddrop.DeadDrop@d769128]
2019-07-14 02:29:20.992 18618-18618/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@9f16479, com.daweber.deaddrop.DeadDrop@6fad5be]
2019-07-14 02:29:37.286 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 1
2019-07-14 02:29:37.287 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {com.daweber.deaddrop.DeadDrop@d769128=com.daweber.deaddrop.DropsListAdapter$1@38e0d6c}
2019-07-14 02:29:37.287 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|15
2019-07-14 02:29:37.766 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 0
2019-07-14 02:29:37.766 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {com.daweber.deaddrop.DeadDrop@6fad5be=com.daweber.deaddrop.DropsListAdapter$1@4049458, com.daweber.deaddrop.DeadDrop@d769128=com.daweber.deaddrop.DropsListAdapter$1@38e0d6c}
2019-07-14 02:29:37.767 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|4
2019-07-14 02:29:40.292 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): 1
    DropID = com.daweber.deaddrop.DeadDrop@6fad5be(ID|15)
    DropsList = [com.daweber.deaddrop.DeadDrop@9f16479, com.daweber.deaddrop.DeadDrop@6fad5be]
2019-07-14 02:29:40.358 18618-18618/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@c9d4e22]
2019-07-14 02:29:40.769 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): -1
    DropID = com.daweber.deaddrop.DeadDrop@9f16479(ID|4)
    DropsList = [com.daweber.deaddrop.DeadDrop@c9d4e22]
2019-07-14 02:30:02.153 18766-18766/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@ff36b61]

Итак, теперь возникает вопрос, как мне изменить эту строку

int pos = deadDrops.indexOf(mDeadDrop);

получить индекс объекта, просмотрев object.getId () вместо подписи объекта?


person Matthew Weber    schedule 13.07.2019    source источник


Ответы (1)


Итак, я нашел способ сделать это методом «грубой силы», который, похоже, предотвращает сбои, но если список вырос до 1000 записей, это может быть не самый эффективный способ, поэтому, если у кого-то есть лучшее решение:

новый запуск ()

@Override
                public void run() {
                    final String TAG_FUN = ".[Runnable]run(): ";
                    // TODO: Here is the problem.
                    for (int i = 0; i <= deadDrops.size(); i++) {
                        DeadDrop d = deadDrops.get(i);
                        if (d.getId() == mDeadDrop.getId()) {
                            int pos = deadDrops.indexOf(d);
                            Log.d(TAG, TAG_CLS + TAG_FUN + pos
                                    + "\nDropID = " + d.toString() + "(ID|" + d.getId() + ")"
                                    + "\nDropsList = " + deadDrops.toString());
                            remove(pos);
                            break;
                        }
                    }
                }
person Matthew Weber    schedule 14.07.2019