For instance, you have an adapter and in onBindViewHolder method you set OnClickListener to some views (and do some actions there depending on view position). You should assign final to position param of method onBindViewHolder so it could be accessible from onClick().
After changing dataset (remove or add item in list) you call onItemInserted or onItemRemoved and this really adds/removes a view in the recyclerview, BUT it does not refresh other viewitems so when you click on a neighbor viewitem it will open a screen or show data with invalid index. To avoid this I basically call notifyDatasetChanged to call onBind to all visible views and remove/add some views.
So how to refresh other views when you call notifyItemInserted/removed or how to work with these methots appropriately?
Assigning the position to a variable in onBindViewHolder will lead to an inconsistent state if items in the dataset are inserted or deleted without calling notifyDataSetChanged.
To use onItemInserted or onItemRemoved the data in the viewholder should remain consistent since it will not be redrawn and onClick would use this invalid position assigned before an item was added or removed.
For this and other use cases the RecyclerView.ViewHolder provides methods to access its position and id:
Use getAdapterPosition() or getItemId() to get valid positions and ids.
Also have a look on the other methods available in RecyclerView.ViewHolder.
So, the way I fix the problem I had is by changing the position into viewHolder.getAdapterPosition()
Cheers!
I advise you to add notifyItemRangeChanged after insert or remove list inside adapter. This work for my project.
Example in remove item :
public void removeItem (int pos) {
simpanList.remove(pos);
notifyItemRemoved(pos);
notifyItemRangeChanged(pos, simpanList.size());//add here, this can refresh position cmiiw
}
For future readers, this is what I do when inserting/removing in recyclerview
For example, my model class is CarsModel
In my adapter
ArrayList<CarsModel> carsModel;
In onBindViewHolder
CardModel model = carsModel.get(position);
When removing data in list using button in holder:
int position = holder.getAdapterPosition();
carsModel.remove(position);
notifyItemRemoved(position);
Then when inserting
carsModel.add(0, model);
notifyItemInserted(0);
or insert in last row
carsModel.add(carsModel.size() - 1 , model);
notifyItemInserted(carsModel.size()-1);
Related
Is there an event called by changing the count of items in the recyclerview?
I want to call function every time recyclerview items are added.
Update:
It turns out I misunderstood the original question. What was really asked is how to get notified when the underlying data in an Adapter has changed.
For this you could use a RecyclerView.AdapterDataObserver in conjunction with RecyclerView.Adapter's registerAdapterDataObserver(AdapterDataObserver observer) method to register for changes in the underlying list data.
Original Answer:
Your RecyclerView is backed by a RecyclerView.Adapter which owns the actual list of items being displayed.
If you change the backing list, you should call one of the notifyXXX methods on your adapter to notify your RecyclerView what has changed (and optionally where in the list the change happened.)
At the very least, you could call notifyDataSetChanged on your adapter to tell your RecyclerView that something somewhere in the list has changed. This can be expensive since you're not being specific about what changed, so the RecyclerView has to query the adapter for more information and potentially redraw its entire client area of items.
Something like:
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
Behind the scenes, the RecyclerView will call the RecyclerView.Adapter#getItemCount() method on the Adapter to determine what the current item count is.
I am working on an Android app which has a recycler view. I have items ordered randomly in the recycler view. I want to move the fifth item of the list to the first position. And move the item previously at the first position to second position. Can anybody help me with this please?
You can use Collections.swap()
Swaps the elements at the specified positions in the specified list. (If the specified positions are equal, invoking this method leaves the list unchanged.)
METHOD
public static void swap(List<?> list,
int i,
int j)
Parameters:
list - The list in which to swap elements.
i - the index of one element to be swapped.
j - the index of the other element to be swapped.
SAMPLE CODE
// first swap the item using Collections.swap() method
Collections.swap(yourList, firstPosition, positionToSwap);
// than notify your adapter about change in list using notifyItemMoved() method
YourAdapter.notifyItemMoved(firstPosition, positionToSwap);
The way RecyclerView works is every time a new item comes on screen it calls your custom RecyclerView.Adapter subclass. Adapter has a reference for the dataset you take data for views from and gets passed the ViewHolder (the layout of an item) and index into onBindViewHolder(MyViewHolder holder, int position) to take dataset[position] and put the data into holder.textview.setText(dataset[position]).
As such, to swap places of elements you have 2 options:
rearrange data in the initial dataset. Chances are it's an ArrayList<> of some kind and you do TEMP=dataset[5];ArrayList.remove(TEMP);ArrayList.insert(TEMP,1); ArrayList will shift everything as required.
if it is important to keep dataset intact, you can rewrite your adapter, so that it keeps a map of { position : dataset_index } and populates items according to that map. That should be trivial.
And then you have to refresh the data with adapter.notifyDataSetChaged();
When an item is added or removed using DiffUtil, inside android.support.v7.util.AdapterListUpdateCallback only the notifyItemRangeInserted(1, 1) or notifyItemRangeRemoved(1,1) respectively is invoked, notifyItemRangeChanged() is not invoked. I set the positions on each of the views using setTag(position) but are not getting updated for the existing items. Wouldn't prefer to update items manually.
list update:
private void updateItems(final List<Feed> newPosts) {
List<Feed> olderPosts = new ArrayList<>(currentPosts);
final CustomDiffCallback DIFF_CALLBACK = new CustomDiffCallback(olderPosts, newPosts);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(DIFF_CALLBACK, true);
}
Turns out that we can't use setTag() and getTag() for passing in position to the views. Since the main purpose of DiffUtil is to minimize the updations for views that haven't changed, the views that already have positions set on them cannot get through an updation pass hence would return stale positions. Though In my case, I had the custom listener in a separate class, the update part was a bit unusual. I had to get getLayoutPosition() of ViewHolder. Also can use getAdapterPosition() for the same.
I have a RecyclerView with its RecyclerView.Adapter and view holder. I am trying to delete an item from list, code as follows inside onClick() on delete button in the ViewHolder
int position = getAdapterPosition();
if(position > -1)
{
Place place = placeList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, getItemCount());
}
Despite removing the view and doing the animation (list also gets affected), the old view (or lower one) still exist or drawn again.
For example, if the list starts with size = 5, then i try to remove index 4, he remove 4, then still draw 5 views.
EDIT
If i remove notifyItemRangeChanged() then it does that bug only if i do the following
1- click on delete
2- click button very quickly that takes me to new view
3- going back to the list where i can delete
4- start deleting, and bug happens. 1 item still remain even though the List size = 0 (getItemCount is called with 0).
If i only call NotifyDataSetChanged(), then it removes item, but view just stays there !!
Any help or suggestion is appreciated.
Thanks.
EDIT complete class LINK
Try this:
placeList.remove(position);
notifyItemRemoved(position);
use below code it will solve your problem.
holder.deleteImg.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(list.size()!=0){
list.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position,list.size());
}
}
});
I've had the same problem, notifyItemRangeChanged() call didn't help while notifyDataSetChanged() did (though stopped the animation). But I am using the ItemTouchHelper on the RecyclerView (to allow for moving items around), and it became obvious that this class was causing the trouble.
The only difference in my case is that to reproduce this overlap-after-delete bug, user has to long press on an item while deleting it.
After I modified the overriden isLongPressDragEnabled() method of ItemTouchHelper.Callback to return false instead of true, the issue was solved.
lastImages.remove(position); (lastImages equals your array list)
newContentAdapter.notifyDataSetChanged();
it is works. You have to remove it in your array not item. then notify adapter. Thats all
for me notifyItemRemoved and notifyDataSetChanged doesnt work. I tried everything , only solution that worked for me is calling or reloading recycler view inside onSwiped method!
you don't have to create new reference of place list.that need to declared as array list. you can't add or remove a object from list. you can do that only with arraylist.
change this lines
private List<Place> placeList;
Place place = placeList.remove(position);
to
private ArrayList<Place> placeList;
// other codes
placeList.remove(position);
// print placeList to confirm
notifyItemRemoved(position);
I had this issue
when I'm using notifyDataSetChanged() it works fine but I wanted the animation so I used notifyItemRemoved(position) for animation then I used notifyDataSetChanged() but I delayed the interval between those functions
I made extension for it you can use it on any RecylcerView
fun RecyclerView.removeItem(position: Int){
adapter?.notifyItemRemoved(position)
handler.postDelayed({
adapter?.notifyDataSetChanged()
},300)
}
You need to update you adapter;
Create method in Adapter class where you will be put your data.
For example : setData(List<Place> data);
When last item in list you need to write: adapter.setData(null);
I had the same problem. Just set list = null. It's all.
I'm having a bit of trouble preserving the scroll position of a list view when changing it's adapter's data.
What I'm currently doing is to create a custom ArrayAdapter (with an overridden getView method) in the onCreate of a ListFragment, and then assign it to its list:
mListAdapter = new CustomListAdapter(getActivity());
mListAdapter.setNotifyOnChange(false);
setListAdapter(mListAdapter);
Then, when I receive new data from a loader that fetches everything periodically, I do this in its onLoadFinished callback:
mListAdapter.clear();
mListAdapter.addAll(data.items);
mListAdapter.notifyDataSetChanged();
The problem is, calling clear() resets the listview's scroll position. Removing that call preserves the position, but it obviously leaves the old items in the list.
What is the proper way to do this?
As you pointed out yourself, the call to 'clear()' causes the position to be reset to the top.
Fiddling with scroll-position, etc. is a bit of a hack to get this working.
If your CustomListAdapter subclasses from ArrayAdapter, this could be the issue:
The call to clear(), calls 'notifyDataSetChanged()'. You can prevent this:
mListAdapter.setNotifyOnChange(false); // Prevents 'clear()' from clearing/resetting the listview
mListAdapter.clear();
mListAdapter.addAll(data.items);
// note that a call to notifyDataSetChanged() implicitly sets the setNotifyOnChange back to 'true'!
// That's why the call 'setNotifyOnChange(false) should be called first every time (see call before 'clear()').
mListAdapter.notifyDataSetChanged();
I haven't tried this myself, but try it :)
Check out: Maintain/Save/Restore scroll position when returning to a ListView
Use this to save the position in the ListView before you call .clear(), .addAll(), and . notifyDataSetChanged().
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
After updating the ListView adapter, the Listview's items will be changed and then set the new position:
mList.setSelectionFromTop(index, top);
Basically you can save you position and scroll back to it, save the ListView state or the entire application state.
Other helpful links:
Save Position:
How to save and restore ListView position in Android
Save State:
Android ListView y position
Regards,
Please let me know if this helps!
There is one more use-case I came across recently (Android 8.1) - caused by bug in Android code. If I use mouse-wheel to scroll list view - consecutive adapter.notifyDataSetChanged() resets scroll position to zero. Use this workaround until bug gets fixed in Android
listView.onTouchModeChanged(true); // workaround
adapter.notifyDataSetChanged();
More details is here: https://issuetracker.google.com/u/1/issues/130103876
In your Expandable/List Adapter, put this method
public void refresh(List<MyDataClass> dataList) {
mDataList.clear();
mDataList.addAll(events);
notifyDataSetChanged();
}
And from your activity, where you want to update the list, put this code
if (mDataListView.getAdapter() == null) {
MyDataAdapter myDataAdapter = new MyDataAdapter(mContext, dataList);
mDataListView.setAdapter(myDataAdapter);
} else {
((MyDataAdapter)mDataListView.getAdapter()).refresh(dataList);
}
In case of Expandable List View, you will use
mDataListView.getExpandableListAdapter() instead of
mDataListView.getAdapter()