Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView and Adapter data updates

Tags:

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:

  1. I don’t want to do that if there’s already an internal queue, and there surely is
  2. 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:

  1. I actually alter the data model (e.g. insert something into the backing array)
  2. I call adapter.notify*()
  3. Recycler receives the call
  4. 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?

like image 706
natario Avatar asked Nov 09 '16 13:11

natario


People also ask

How do I update my RecyclerView adapter data?

Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project. Step 2 − Open build. gradle and add Recycler view & Card view library dependencies. Step 3 − Add the following code to res/layout/activity_main.

How do I refresh my kotlin adapter?

To refresh the ListView in Android, call notifyDataSetChanged() method on the Adapter that has been set with the ListView.


2 Answers

I thought about similar questions before, and I decided:

  1. 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. 
  2. 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()); 
  3. If I want to insert more than 1 item to different position of list, similar as 2.

Hope this can help.

like image 53
ywwynm Avatar answered Oct 01 '22 11:10

ywwynm


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;

like image 41
GensaGames Avatar answered Oct 01 '22 11:10

GensaGames