I'm using a RecyclerView and .notifyItemInserted only inserts one item into the RecyclerView. How would I do that say, for multiple items? I want to AVOID using .notifyDataSetChanged().
Say I have 25 items, I want to insert another 20, what method would I call?
You can use notifyItemRangeInserted to notify for multiple insertion.
void notifyItemRangeInserted (int positionStart, int itemCount)
According official documentation
Notify any registered observers that the currently reflected itemCount
items starting at positionStart have been newly inserted. The items
previously located at positionStart and beyond can now be found
starting at position positionStart + itemCount.
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();
I have 500 above records in the list and i am using pagination to load 10 records each time, animation applied to the recyclerview what i want to do is when i am using notifyDataChanged its refresh all items of recyclerview, due to this reason my applied animation not working properly as i want.
Any idea how to refresh only new data in the recyclerview so that all items of recyclerview will not change.
You can use this method to update only newly added data in adapter,
adapter.notifyItemRangeChanged(int positionStart, int itemCount);
or use
adapter.notifyItemRangeInserted(int positionStart, int itemCount);
here,
positionStart: is a starting position from where you've inserted data (list size for most cases when inserted at end of list)
itemCount: is number of items inserted to list
More from here
notifyItemRangeInserted
Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted. The items previously located at positionStart and beyond can now be found starting at position positionStart + itemCount.
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.
positionStart: Position of the first item that was inserted
itemCount: Number of items inserted
notifyItemRangeChanged
Notify any registered observers that the itemCount items starting at position positionStart have changed. Equivalent to calling notifyItemRangeChanged(position, itemCount, null);.
This is an item change event, not a structural change event. It indicates that any reflection of the data in the given position range is out of date and should be updated. The items in the given range retain the same identity.
positionStart: Position of the first item that has changed
itemCount: Number of items that have changed
Different is that notifyItemRangeInserted treats old data as up to date
Use notifyItemRangeChanged() method, like if you have 10 items already and when you scroll down 10 new items added to the array then you should do:
notifyItemRangeChanged(10,array.size())
Which is : notifyItemRangeChanged(int positionStart, int itemCount)
You should use notifyItemRangeChanged because it reset items only between given range. rather than notifyDataSetChanged which reset all the items of RecyclerView.
Yes you can do that by specifying on which position you inserted the new data by calling
RecyclerView.Adapter#notifyItemInserted(int)
you can use DiffUtil to refresh particular item changes.
for reference check this https://proandroiddev.com/diffutil-is-a-must-797502bc1149
This is a question about RecyclerView internal behavior for someone that knows its mechanics or is willing to dig into the source code. I’d like an answer backed up by references to the source.
Original question
(scroll down to ‘In other words’ for a more focused question)
I need to understand how notify* actions (for example, notifyItemInserted()) are enqueued. Imagine I have an adapter backed up by this list:
ArrayList<String> list = Arrays.asList("one", "three", "four");
I want to add the values zero and two, that are missing.
Example 1
list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);
// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);
This is pretty straightforward and clear, nothing to tell.
Example 2
But what if the two actions are very close to each other, and there’s no layout pass in between?
list.add(1, "two");
list.add(0, "zero”);
What should I do now?
adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);
Or maybe
adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);
? From the adapter perspective, the list immediately switched from one, three, four to zero, one, two, three, four so the second option seems more reasonable.
Example 3
list.add(0, “zero”);
adapter.notifyItemInserted(0);
list.add(2, “two”);
adapter.notifyItemInserted(...)
What about it now? 1 or 2 ? The list was updated immediately after, but I am sure there was no layout pass in between.
Question
You got the main issue, and I want to know how should I behave in these situations. The real case is that I have multiple asynchronous tasks ending up in an insert() method. I can enqueue their operations, but:
I don’t want to do that if there’s already an internal queue, and there surely is
I don’t know what happens if two actions happen without a layout pass in between, see Example 3.
In other words
To update recycler, 4 actions must happen:
I actually alter the data model (e.g. insert something into the backing array)
I call adapter.notify*()
Recycler receives the call
Recycler performs the action (e.g. calls getItem*() and onBind() on the adapter) and lays out the change.
It’s easy to understand this when there’s no concurrency, and they happen in sequence:
1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...
Let’s see what happens between steps.
Between 1. and 2.: I would say it is the developer responsibility to call notify() immediately after having altered the data. That’s OK.
Between 2. and 3.: This happens immediately, no issue here.
Between 3. and 4.: This does not happen immediately! AFAIK. So it perfectly possible that a new update (steps 1 and 2) comes between steps 3 and 4 of the previous update.
I want to understand what happens in this case.
How should we behave?
Should I ensure that step 4 of the previous update did took place before inserting new stuff? If so how?
I thought about similar questions before, and I decided:
If I want to insert more than 1 item directly to end of list and
want to get a animation for all, I should:
list.add("0");
list.add("1");
adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
If I want to insert more than 1 item directly to end of list, but
want to get separated animation for each inserted item, I should:
list.add("0");
list.add("1");
adapter.notifyItemInserted(0);
mRecyclerView.postDelayed(new Runnable() {
#Override
public void run() {
// before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged.
adapter.notifyItemInserted(1);
}
}, mRecyclerView.getItemAnimator().getAddDuration());
If I want to insert more than 1 item to different position of list,
similar as 2.
Hope this can help.
So lets start from little intro to RecyclerView works with notify items. And works pretty simple with other list of saved ViewGroup items (ListView for ex.)
RecyclerView has Queue of View Items which already drawn. And doesn't know about any your updates, without calling notify(...) methods. When you added new Items and notify RecyclerView, it starts cycle for checking all Views one by one.
RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)
// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)
// In RecyclerView queue in position-2 was item view-2,
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)
// And again same behavior
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)
// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)
It's just main flow of working notify functions, but what should know all this actions happens on UI Thread, Main Thread, even you can calling updating from Async Tasks. And answering you 2 Question - You can call Notify to the RecyclerView as much as you want, and make sure, you action would be on the correct Queue.
RecyclerView works correct in any usage, more complicated questions would be to your Adapter work. First of all, you need to synchronize you Adapter action, like adding removing items, and totally refuse of index usage. For example, it's would be better for your Example 3
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions
UPDATE |
Related to RecyclerView.Adapter Doc, where you can see functions same with notifyDataSetChanged(). And where this RecyclerView.Adapter invokes child items with android.database.Observable extensions, see more About Observable. Access to this Observable Holder is synchronized, until View Element in RecyclerView release usage.
See also RecyclerView from support library version 25.0 Lines 9934 - 9988;
It should not be a problem if you make multiple updates between layout passes. The RecyclerView is designed to handle (and optimize) this case :
RecyclerView introduces an additional level of abstraction between the
RecyclerView.Adapter and RecyclerView.LayoutManager to be able to
detect data set changes in batches during a layout calculation. [...]
There are two types of position related methods in RecyclerView:
layout position: Position of an item in the latest layout calculation. This is the position from the LayoutManager's
perspective.
adapter position: Position of an item in the adapter. This is the position from the Adapter's perspective.
These two positions are the same except the time between dispatching
adapter.notify* events and calculating the updated layout.
In your case the steps are :
You update the data layer
You call adapter.notify*()
The recyclerview record the change (in AdapterHelper.mPendingUpdates if I understand the code correctly). This change will be reflected in ViewHolder.getAdapterPosition(), but not yet in ViewHolder.getLayoutPosition().
At some point the recyclerView apply the recorded changes, basically it reconcile the layout's point of view with the adapter's point of view. It seems that this can happen before the layout pass.
The 1., 2., 3. sequence can happen any number of times as long as 2. immediately follows 1. (and both happen on the main thread).
(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4.
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
I have a item in RecyclerView which does not need to be updated when I call notifyDataSetChanged.
But I didn't find any methods to make the specified item avoid updated. Is it possible to do this ?
I need to do this because the item is a WebView, it had loaded a html page, if I called notifyDataSetChanged, this item will flash.
The reason why I post this question is avoiding the flash of the WebView when notifyDataSetChanged (see this quesion). After the failure of using notifyItemRangeXXX methods, I post this question.
But after checking your answers and comments, it seems that it's impossible to avoid updating when using notifyDataSetChanged.
Yes, its actually possible to do so. notifyDataSetChanged() notifies the RecyclerView that all the elements in the data set has changed.
As you do not want that to happen and exclude specific items.
for(int i = 0; i< mAdapter.getItemCount(); i++){
if(i != itemPositionToExclude){
mAdapter.notifyItemChanged(i);
}
}
So, we need to loop through all the elements in the adapter and call notifyItemChanged() only for those positions which you do not want to exclude.
The purpose of notifyDataSetChanged IS to update ALL items. Don't use it if you don't want this behavior!
The correct approach would be to only update the data you've changed. There are some methods which will help you to only invalidate the desired data:
notifyItemChanged(int position)
notifyItemInserted(int position)
notifyItemMoved(int fromPosition, int toPosition)
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeRemoved(int positionStart, int itemCount)
notifyItemRemoved(int position)
You should think in different way - which items need to be updated. To update UI of item, after data has changed use method notifyitemchanged