Home » Android » android – Best practice to asynchronously delete a ListView/RecyclerView item

android – Best practice to asynchronously delete a ListView/RecyclerView item

Posted by: admin June 15, 2020 Leave a comment

Questions:

I have a ListView with a CursorLoader. The user can open ListView items (open another Fragment) or delete items. All database actions occur asynchronously and usually it takes a fraction of a second. But technically the user could delete an item and then open the item before the deletion callback and cause and error. What’s the best way to handle this? Here are the options I see.

  1. Just assume that the AsyncTask will always happen quickly enough to avoid a problem
  2. Do the database operation on the UI thread
  3. Invalidate the ListView before the AsyncTask (but this would cause a flash in the UI)
  4. Block user input somehow during the AsyncTask

Edit: I ended up using RecyclerView but I can’t call adapter.notifyItemRemoved(itemPos) until after I have deleted the item from the database.

How to&Answers:

Best option in your case will be using RecyclerView. Because when you clicked delete you can call adapter.notifyItemRemoved(itemPos). So the list item will be removed with animation from your RecyclerView and you will not worry about the delete operation result.

See the Android Developer tutorial on Creating Lists and Cards:

Use the RecyclerView widget when you have data collections whose
elements change at runtime based on user action or network events.

Here is an example:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener, View.OnLongClickListener {

private ArrayList<String> mDataset;
private static Context sContext;

public MyAdapter(Context context, ArrayList<String> myDataset) {
    mDataset = myDataset;
    sContext = context;
}

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);

    ViewHolder holder = new ViewHolder(v);
    holder.mNameTextView.setOnClickListener(MyAdapter.this);

    holder.mNameTextView.setTag(holder);

    return holder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    holder.mNameTextView.setText(mDataset.get(position));

}

@Override
public int getItemCount() {
    return mDataset.size();
}


@Override
public void onClick(View view) {
    ViewHolder holder = (ViewHolder) view.getTag();
    if (view.getId() == holder.mNameTextView.getId()) {
        **//Important !!!**
        notifyItemRemoved(getPosition());
        // Make your database operation also here
    }
}



public static class ViewHolder extends RecyclerView.ViewHolder {
    public TextView mNumberRowTextView;
    public TextView mNameTextView;


    public ViewHolder(View v) {
        super(v);

        mNameTextView = (TextView) v.findViewById(R.id.nameTextView);
    }
}

Answer:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final View itemView = convertView != null ? convertView : /* inflate view */;
        itemView.findViewById(R.id.delete_button).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        itemView.setEnabled(false);
                        // OR view.setClickable(false);
                    }
                });
        return itemView;
    }

Or use RecyclerView with removal animation.

build.gradle

compile 'com.android.support:recyclerview-v7:23.2.1'

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</RelativeLayout>

my_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/value_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"/>

</LinearLayout>

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<String> items = new ArrayList<>(
            Arrays.asList("first", "second", "third"));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView myRecyclerView = (RecyclerView)
                findViewById(R.id.my_recycler_view);
        myRecyclerView.setAdapter(new MyAdapter());
    }

    private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(LayoutInflater.from(MainActivity.this)
                    .inflate(R.layout.my_item, parent, false));
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.bind(position);
        }

        @Override
        public int getItemCount() {
            return items.size();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {

            private TextView valueTextView;
            private Button deleteButton;

            public MyViewHolder(View itemView) {
                super(itemView);
                valueTextView = (TextView) itemView.findViewById(R.id.value_text);
                deleteButton = (Button) itemView.findViewById(R.id.delete_button);
                deleteButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int position = (int) v.getTag();
                        items.remove(position);
                        notifyItemRemoved(position);
                        notifyItemRangeChanged(position, items.size());
                    }
                });
            }

            public void bind(int position) {
                valueTextView.setText(items.get(position));
                deleteButton.setTag(position);
            }
        }
    }
}

Answer:

if your database query is not returning lot of data then there is no issue to do database operations on UI thread.