Android RecyclerView addition & removal of items
Asked Answered
L

20

170

I have a RecyclerView with an TextView text box and a cross button ImageView. I have a button outside of the recyclerview that makes the cross button ImageView visible / gone.

I'm looking to remove an item from the recylerview, when that items cross button ImageView is pressed.

My adapter:

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.setOnLongClickListener(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()) {
            Toast.makeText(sContext, holder.mNameTextView.getText(), Toast.LENGTH_SHORT).show();
        }
    }


    @Override
    public boolean onLongClick(View view) {
        ViewHolder holder = (ViewHolder) view.getTag();
        if (view.getId() == holder.mNameTextView.getId()) {
            mDataset.remove(holder.getPosition());

            notifyDataSetChanged();

            Toast.makeText(sContext, "Item " + holder.mNameTextView.getText() + " has been removed from list",
                    Toast.LENGTH_SHORT).show();
        }
        return false;
    }

    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);
        }
    }
}

My layout is:

<?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"
    android:gravity="center_vertical"
    android:id="@+id/layout">

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:padding="5dp"
        android:background="@drawable/greyline"/>

    <ImageView
        android:id="@+id/crossButton"
        android:layout_width="16dp"
        android:layout_height="16dp"
        android:visibility="gone"
        android:layout_marginLeft="50dp"
        android:src="@drawable/cross" />
</LinearLayout>

How can I get something like an onClick working for my crossButton ImageView? Is there a better way? Maybe changing the whole item onclick into a remove the item? The recyclerview shows a list of locations that need to be edited. Any technical advice or comments / suggestions on best implementation would be hugely appreciated.

Lazaro answered 27/9, 2014 at 16:51 Comment(3)
Take a look LINK 1 LINK 2 it may help UOstracod
#38222910Gentes
You can see this example in Github Happy code!!!!Dinka
A
313

I have done something similar. In your MyAdapter:

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    public CardView mCardView;
    public TextView mTextViewTitle;
    public TextView mTextViewContent;
    public ImageView mImageViewContentPic;
    
    public ImageView imgViewRemoveIcon;
    public ViewHolder(View v) {
        super(v);
        mCardView = (CardView) v.findViewById(R.id.card_view);
        mTextViewTitle = (TextView) v.findViewById(R.id.item_title);
        mTextViewContent = (TextView) v.findViewById(R.id.item_content);
        mImageViewContentPic = (ImageView) v.findViewById(R.id.item_content_pic);
        //......
        imgViewRemoveIcon = (ImageView) v.findViewById(R.id.remove_icon);

        mTextViewContent.setOnClickListener(this);
        imgViewRemoveIcon.setOnClickListener(this);
        v.setOnClickListener(this);
        mTextViewContent.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (mItemClickListener != null) {
                    mItemClickListener.onItemClick(view, getPosition());
                }
                return false;
            }
        });
    }


    @Override
    public void onClick(View v) {
        //Log.d("View: ", v.toString());
        //Toast.makeText(v.getContext(), mTextViewTitle.getText() + " position = " + getPosition(), Toast.LENGTH_SHORT).show();
        if(v.equals(imgViewRemoveIcon)){
            removeAt(getPosition());
        }else if (mItemClickListener != null) {
            mItemClickListener.onItemClick(v, getPosition());
        }
    }
}

public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
    this.mItemClickListener = mItemClickListener;
}
public void removeAt(int position) {
    mDataset.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, mDataSet.size());
}

Edit:

getPosition() is deprecated now, use getAdapterPosition() instead.

Alfano answered 11/10, 2014 at 3:6 Comment(18)
you will also want to add notifyItemRangeChanged(getPosition, mDataSet.size()); to paradite's answer after you remove the item so that all the views below the item removed will adjust accordingly.Liva
I see you have also called notifyItemRemoved, is removeAt to be invoked via the UI thread for the UI to update, or can it be called by any thread and will automatically update the UI?Crews
i have the same problem but getPosition() is now deprecatedHough
Thanks, apparently just calling notifyItemRemoved(position) wasn't enough.Keynes
@Hough Use getAdapterPosition()Grimonia
this code doesn't fix a problem when you quickly remove items from both top and below.Lonlona
Thanks for the hint with the notifyItemRangeChanged(), that was giving me quite a headacheAerosphere
@Hough use getAdapterPosition() insteadWinding
I Only need call this function after notifyItemRemoved(position); -> notifyItemRangeChanged(position, mDataSet.size()); Thanks !Bohaty
very thanks for :notifyItemRangeChanged(position, mDataSet.size());Dinsmore
NotityItemRangeChanged is not necessary when we are removing just a single item at a time. So i think just keeping it simple with notifyItemRemoved is enough to go with.Crenellate
This does not help in case of removing the last element.Pneumodynamics
@ChrisAngell Thanks for mentioning! Why is this not implemented in the "notifyItemRemoved" method? ... oh Google :(Switchboard
I also set the visibility of the view to View.GONE before calling remove function in the case where the user double taps the delete button.Graver
For double click problem (index out of bounds exception) you can use ` if (holder.getAdapterPosition() != RecyclerView.NO_POSITION) { //your code here } in OnClickMethodHeyduck
@Alfano can you update your answer as it's out of date please?Devondevona
Cannot call this method while RecyclerView is computing a layout or scrolling getting this when called!Dripdry
what if the remote method without index given? I mean if we clear all items? @MoisoniIoanUnload
B
77

first of all, item should be removed from the list!

  mDataSet.remove(getAdapterPosition());

then:

  notifyItemRemoved(getAdapterPosition());
  notifyItemRangeChanged(getAdapterPosition(), mDataSet.size()-getAdapterPosition());
Bibbye answered 3/7, 2016 at 7:55 Comment(3)
thanks your overmuch, suppose i have to add more values means what can i do for that.Silveira
it`s related to size of the list in your adapter.@Hammad NasirBibbye
I did same, but recyclerview height not dynamicaly changed. mNotes.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, getItemCount()); @Bibbye did I missed anything.Stetson
S
32

if still item not removed use this magic method :)

private void deleteItem(int position) {
        mDataSet.remove(position);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, mDataSet.size());
        holder.itemView.setVisibility(View.GONE);
}

Kotlin version

private fun deleteItem(position: Int) {
    mDataSet.removeAt(position)
    notifyItemRemoved(position)
    notifyItemRangeChanged(position, mDataSet.size)
    holder.itemView.visibility = View.GONE
}
Surrebutter answered 20/11, 2016 at 22:59 Comment(5)
@Mostafa: Suppose I have 10 item and I remove the last one, in that scenario holder.itemView.setVisibility(View.GONE), Hide all data,Stetson
@HiteshDhamshaniya no it just hide and gone the last viewSurrebutter
No need to hide the view. dataSet.remove(position) works fine, but yoi need to call notifyDatasetChamged() or notifyItemRemoved(dataset.size() - 1)Taw
thanks for holder.itemView.setVisibility(View.GONE);Gilmore
This is not correct; remember that ViewHolders are recycled, so setting the visibility to GONE will leave it set to GONE on some other item; also, RecyclerView does not actually respect the GONE property when laying out views.Shangrila
W
27

The Problem

RecyclerView was built to display data in an efficient and responsive manner. Usually you have a dataset which is passed to your adapter and is looped through to display your data. Here your dataset is:

private ArrayList<String> mDataset;

The point is that RecyclerView is not connected to your dataset, and therefore is unaware of your dataset changes.

It just reads data once and displays it through your ViewHolder, but a change to your dataset will not propagate to your UI.

This means that whenever you make a deletion/addition on your data list, those changes won't be reflected to your RecyclerView directly. (i.e. you remove the item at index 5, but the 6th element remains in your recycler view).

A (old school) solution

RecyclerView exposes some methods for you to communicate your dataset changes, reflecting those changes directly on your list items.

The standard Android APIs allow you to bind the process of data removal (for the purpose of the question) with the process of View removal.

The methods we are talking about are:

notifyItemChanged(index: Int)
notifyItemInserted(index: Int)
notifyItemRemoved(index: Int)
notifyItemRangeChanged(startPosition: Int, itemCount: Int)
notifyItemRangeInserted(startPosition: Int, itemCount: Int)
notifyItemRangeRemoved(startPosition: Int, itemCount: Int)

A Complete (old school) Solution

If you don't properly specify what happens on each addition, change or removal of items, RecyclerView list items are animated unresponsively because of a lack of information about how to move the different views around the list.

The following code will allow RecyclerView to precisely play the animation with regards to the view that is being removed (And as a side note, it fixes any IndexOutOfBoundExceptions, marked by the stacktrace as "data inconsistency").

void remove(position: Int) {
    dataset.removeAt(position)
    notifyItemChanged(position)
    notifyItemRangeRemoved(position, 1)
}

Under the hood, if we look into RecyclerView we can find documentation explaining that the second parameter we pass to notifyItemRangeRemoved is the number of items that are removed from the dataset, not the total number of items (As wrongly reported in some others information sources).

    /**
     * Notify any registered observers that the <code>itemCount</code> items previously
     * located at <code>positionStart</code> have been removed from the data set. The items
     * previously located at and after <code>positionStart + itemCount</code> may now be found
     * at <code>oldPosition - itemCount</code>.
     *
     * <p>This is a structural change event. Representations of other existing items in the data
     * set are still considered up to date and will not be rebound, though their positions
     * may be altered.</p>
     *
     * @param positionStart Previous position of the first item that was removed
     * @param itemCount Number of items removed from the data set
     */
    public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
    }

Open source solutions

You can let a library like FastAdapter, Epoxy or Groupie take care of the business, and even use an observable recycler view with data binding.

New ListAdapter

Google recently introduced a new way of writing the recycler view adapter, which works really well and supports reactive data.

It is a new approach and requires a bit of refactoring, but it is 100% worth switching to it, as it makes everything smoother.

here is the documentation, and here a medium article explaining it

Wampum answered 19/6, 2018 at 11:15 Comment(0)
P
11

Here are some visual supplemental examples. See my fuller answer for examples of adding and removing a range.

Add single item

Add "Pig" at index 2.

Insert single item

String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);

Remove single item

Remove "Pig" from the list.

Remove single item

int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);
Parasitism answered 24/2, 2018 at 4:15 Comment(0)
G
7

Possibly a duplicate answer but quite useful for me. You can implement the method given below in RecyclerView.Adapter<RecyclerView.ViewHolder> and can use this method as per your requirements, I hope it will work for you

public void removeItem(@NonNull Object object) {
        mDataSetList.remove(object);
        notifyDataSetChanged();
    }
Germanic answered 26/8, 2017 at 20:59 Comment(0)
H
4

I tried all the above answers, but inserting or removing items to recyclerview causes problem with the position in the dataSet. Ended up using delete(getAdapterPosition()); inside the viewHolder which worked great at finding the position of items.

Hermy answered 7/7, 2016 at 7:11 Comment(1)
Just set a listener and use getAdapterPosition() method inside view holder. It will return the position of the item.Hermy
T
4

The problem I had was I was removing an item from the list that was no longer associated with the adapter to make sure you are modifying the correct adapter you can implement a method like this in your adapter:

public void removeItemAtPosition(int position) {
    items.remove(position);
}

And call it in your fragment or activity like this:

adapter.removeItemAtPosition(position);
Tendentious answered 29/9, 2016 at 17:29 Comment(0)
B
3
  public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List<cardview_widgets> list;

public MyAdapter(Context context, List<cardview_widgets> list) {
    this.context = context;
    this.list = list;
}

@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    View view = LayoutInflater.from(this.context).inflate(R.layout.fragment1_one_item,
            viewGroup, false);
    return new MyViewHolder(view);
}

public static class MyViewHolder extends RecyclerView.ViewHolder {
    TextView txtValue;
    TextView txtCategory;
    ImageView imgInorEx;
    ImageView imgCategory;
    TextView txtDate;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        txtValue= itemView.findViewById(R.id.id_values);
        txtCategory= itemView.findViewById(R.id.id_category);
        imgInorEx= itemView.findViewById(R.id.id_inorex);
        imgCategory= itemView.findViewById(R.id.id_imgcategory);
        txtDate= itemView.findViewById(R.id.id_date);
    }
}

@NonNull
@Override
public void onBindViewHolder(@NonNull final MyViewHolder myViewHolder, int i) {

    myViewHolder.txtValue.setText(String.valueOf(list.get(i).getValuee()));
    myViewHolder.txtCategory.setText(list.get(i).getCategory());
    myViewHolder.imgInorEx.setBackgroundColor(list.get(i).getImg_inorex());
    myViewHolder.imgCategory.setImageResource(list.get(i).getImg_category());
    myViewHolder.txtDate.setText(list.get(i).getDate());
    myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            list.remove(myViewHolder.getAdapterPosition());
            notifyDataSetChanged();
            return false;
        }
    });
}

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

i hope this help you.

Bidle answered 24/1, 2019 at 13:53 Comment(0)
C
2

if you want to remove item you should do this: first remove item:

phones.remove(position);

in next step you should notify your recycler adapter that you remove an item by this code:

notifyItemRemoved(position);
notifyItemRangeChanged(position, phones.size());

but if you change an item do this: first change a parameter of your object like this:

Service s = services.get(position);
s.done = "Cancel service";
services.set(position,s);

or new it like this :

Service s = new Service();
services.set(position,s);

then notify your recycler adapter that you modify an item by this code:

notifyItemChanged(position);
notifyItemRangeChanged(position, services.size());

hope helps you.

Castano answered 25/10, 2017 at 10:58 Comment(0)
V
1
  String str = arrayList.get(position);
  arrayList.remove(str);
  MyAdapter.this.notifyDataSetChanged();
Verst answered 29/9, 2017 at 5:58 Comment(2)
This is a really bad idea, as you will force a full re-draw of the items, with probably noticeable lag if you have a lot of items. The correct thing to do is to call notifyItemRemoved(position).Sindhi
ya your correct we also use notifyItemRemoved(position) instated of notifyDataSetChanged()Verst
T
1

Incase Anyone wants to implement something like this in Main class instead of Adapter class, you can use:

public void removeAt(int position) {
    peopleListUser.remove(position);

    friendsListRecycler.getAdapter().notifyItemRemoved(position);
    friendsListRecycler.getAdapter().notifyItemRangeChanged(position, peopleListUser.size());
}

where friendsListRecycler is the Adapter name

Tropho answered 8/2, 2019 at 15:52 Comment(0)
C
1

To Method onBindViewHolder Write This Code

holder.remove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Cursor del=dbAdapter.ExecuteQ("delete from TblItem where Id="+values.get(position).getId());
                values.remove(position);
                notifyDataSetChanged();
            }
        });
Chloris answered 13/7, 2019 at 13:41 Comment(0)
Z
1

you must to remove this item from arrayList of data

myDataset.remove(holder.getAdapterPosition());
notifyItemRemoved(holder.getAdapterPosition());
notifyItemRangeChanged(holder.getAdapterPosition(), getItemCount());
Zook answered 21/1, 2021 at 22:40 Comment(0)
C
0

make interface into custom adapter class and handling click event on recycler view..

 onItemClickListner onItemClickListner;

public void setOnItemClickListner(CommentsAdapter.onItemClickListner onItemClickListner) {
    this.onItemClickListner = onItemClickListner;
}

public interface onItemClickListner {
    void onClick(Contact contact);//pass your object types.
}
    @Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
    // below code handle click event on recycler view item.
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            onItemClickListner.onClick(mContectList.get(position));
        }
    });
}

after define adapter and bind into recycler view called below code..

        adapter.setOnItemClickListner(new CommentsAdapter.onItemClickListner() {
        @Override
        public void onClick(Contact contact) {
            contectList.remove(contectList.get(contectList.indexOf(contact)));
            adapter.notifyDataSetChanged();
        }
    });
}
Customer answered 21/8, 2018 at 7:34 Comment(0)
E
0

In the activity:

mAdapter.updateAt(pos, text, completed);
mAdapter.removeAt(pos);

In the your adapter:

void removeAt(int position) {
    list.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, list.size());
}

void updateAt(int position, String text, Boolean completed) {
    TodoEntity todoEntity = list.get(position);
    todoEntity.setText(text);
    todoEntity.setCompleted(completed);
    notifyItemChanged(position);
}
Epictetus answered 27/11, 2019 at 4:33 Comment(0)
O
0

in 2022, after trying everything the whole internet given below is the answer

In MyViewHolder class

private myAdapter adapter;

inside MyViewHolder function initalise adapter

adapter = myAdapter.this

inside onclick

int position = getAdapterPosition()
list.remove(position);
            adapter.notifyItemRemoved(position);
Onstage answered 26/6, 2022 at 9:6 Comment(0)
U
0

Part of methods are deprecated in RecyclerView. So relevant for use is viewHolder.getAbsoluteAdapterPosition() or viewHolder.getBindingAdapterPosition()

simplified code snippet inside any click listener or callback:

val adapterPosition = viewHolder.getBindingAdapterPosition()
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
Use answered 14/8, 2023 at 23:59 Comment(0)
K
-1
 //////// set the position
 holder.cancel.setTag(position);


///// click to remove an item from recycler view and an array list
holder.cancel.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {

            int positionToRemove = (int)view.getTag(); //get the position of the view to delete stored in the tag
            mDataset.remove(positionToRemove);
            notifyDataSetChanged();
                }
            });
Kinetic answered 10/7, 2018 at 5:27 Comment(1)
This is wildly inefficient. You know the position being removed; there is no reason to do a notifyDataSetChanged . Also, this is very risky: the position will change as items are moved around, and it may not be rebound if you're using granular updates like you should be. Use getAdapterPosition() instead of this hack with the tag; that's why it exists.Shangrila
T
-1

In case you are wondering like I did where can we get the adapter position in the method getadapterposition(); its in viewholder object.so you have to put your code like this

mdataset.remove(holder.getadapterposition());
Tarah answered 30/10, 2019 at 18:22 Comment(1)
You also need to notify the adapter, otherwise it won't update.Shangrila

© 2022 - 2024 — McMap. All rights reserved.