notifyDataSetChanged() not being called when underlying Realm is changed using RealmRecyclerViewAdapter - android

I have a set of data in a Realm that is displayed in a RecyclerView using a RealmRecyclerViewAdapter. Basically, I'll have 25 elements displayed in the RecyclerView -- if items are being inserted into the Realm for the first time there are no issues. It seems to work properly if the Realm isn't being added to shortly after all its data has been deleted because the issue only arises when I need to delete all the items in the Realm and then add a new Collection of items shortly afterwards.
What will happen is I will send an http request for the new items, and on receiving them, I'll delete all the items currently in the Realm, and then add the new ones, both in different transactions; I've also tried doing it in the same transaction.
The unexpected behavior is that the contents of the RecyclerView will flicker for a very brief moment, as if all the items are about to be rebound, and I can even see the data of the new items being shown for a split second, but then the contents of the RecyclerView just goes back to showing the previous data -- the data I know to be deleted from the Realm. I know it's deleted because if I interact with the RecyclerView after this flicker, even just a click on a static view, all the items will be instantly rebound and reflect the new items that have been added; as if notifyDataSetChanged() is finally being called.
This behavior isn't always exhibited. Most of the time the items are all deleted, the new ones are added, and it just works perfectly; no flicker. But as far as the user behavior goes when it works properly and when it doesn't, there is nothing different. The same thing can be done over and over again and sometimes it will work and other times it won't.
I've tried deleting the items using mRealm.deleteAll() and mOrderedRealmList.deleteAllFromRealm(). And I've tried adding the items to the collection using both mRealm.insert(items) and mRealm.copyToRealm(items). I've also tried manually calling notifyDataSetChanged() only after the items are added, only after the items are all deleted, and after both events, but the issue still occurs.
Any idea on what could be causing this behavior?
This is the only code that is run that manipulates Realm data:
mRealm.beginTransaction();
mOrderedRealmCollection().deleteAllFromRealm();
mRealm.commitTransaction();
...
...
processing of new items...
...
...
mRealm.beginTransaction();
mRealm.insert(items);
mRealm.commitTransaction();
Update:
When I delete all the items from the Realm and add the new items within the same transaction the unexpected behavior occurs more frequently. Also, I've noticed that when the changes are made in the same transaction and the issue occurs, sometimes there is no flicker, the RecyclerView just continues to display the old data, uninterrupted, but if I touch the RecyclerView anywhere, just like when the flicker does occur, all the items will be instantly rebound and reflect the new items that have been added.
Update: How Adapter is Set Up
//in onCreate() method
mItems = mRealm.where(Item.class).findAllSorted("mIndex", Sort.ASCENDING);
//in onCreateView() method
mRecyclerView.setAdapter(mAdapter = new MyRealmRecyclerViewAdapter(this, mItems));
Update: Cancel Button onClick() Code
private void setListeners(){
mCancelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mSearchEditText.clearFocus();
mCancelButton.setVisibility(View.GONE);
mCreateItemButton.setVisible(true);
Toast.makeText(A_Main.this,"Cancel clicked",Toast.LENGTH_SHORT).show();
}
});

Related

LiveData is duplicating MutableList

I want to update my Recyclerview items whenever there is a data change in Firestore.
So my activity have the following code:
taskViewModel.fetchedTaskLiveData.observe(
this, Observer {
if (it != null) {
todoListAdapter.setListData(it)
showRecyclerView()
}
Inside adapter setListData method:
fun setListData(data: MutableList<Todo>) {
//this.todoList.clear()
this.todoList.addAll(data)
notifyDataSetChanged()
}
In this situation, whenever a changed list is fetched, Recyclerview is duplicating and adding the changed
list below the old list.
And if "this.todoList.clear()" is uncommented, list is getting cleared and no data is showing on data change.
I tried all possible solutions, but I think somewhere am missing a part. stuck since a long time.
Thanks a lot for all kind of suggestion.
So I was stuck with this and found out after 12 hours a tiny change.
I corrected it by changing for with forEach and that changed the game.
Also I am clearing list items before adding the items.
todoList.clear()

RecyclerView and Adapter data updates

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

SwipeMenuListView new Row added is showing First time data

I've read many of the previous posts on this topic, but i'm not getting it right.
I have an adapter which has a private values of the items in the list.
When i update the values(add a new items), i watch the value in debugger and the "getView" func and see that the value is correct.
BUT the actual rowView i see is just the first item in the list.
I have no clue what may cause this.
This listview is on the same activity while i show a different layout and hide the listview to add a new item.
Can there be a connection while the listview visibility is "GONE"?
When i remove items from it it updates listview fine(that is done when listview is visible).
private void updateAdapter() {
this.values.clear();
this.values.addAll(staticlistIndifferentclass);
notifyDataSetChanged();
}
~~~~UPDATE~~~~
Ok,
So i discovered the cause of the problem, though i'm not sure why it is.
The code was fine the way it was with regular Listview but the bug is on:
com.baoyz.swipemenulistview.SwipeMenuListView
Try use ListView.invalidateViews
This should cause views rebuilding
Use new Adapter object like this :
listView.setAdapter(new yourCustomAdapter());
When you delete all dataSet items ,it is better to bind new Adapter object .

Android listview refresh only once

I am really desperate, please help me.
In my main Activity I want to remove all items from the listview then add other data. I am able to delete the data and refresh the listview, but when I add new data, something strange happens. I insert the data in my Array. I call notifydatasetchanged on my adapter after every insertion. The adapter gets those items (I have confirmed this in debug mode). But the listview will not refresh. And now comes the VERY STRANGE thing: When I tap the back button on my device, the application quits (this part is ok), but for a moment before quitting it refreshes the listview with the correct data.
Here are some parts of my code:
For deleting the data I use:
newsArrayList = new ArrayList<NewsClass>();
Adapter = new NewsAdapter(this, newsArrayList);
lv.setAdapter(Adapter);
"lv" is my listview. The listview correctly loses all items and refreshes. I have also tried this:
newsArrayList.clear();
Adapter.notifyDataSetChanged();
Also worked like expected. Then I use a "for" loop to add new items to my array, where I have the following lines:
newsArrayList.add(news[i]);
Adapter.notifyDataSetChanged();
As I wrote, when running in debug mode, everything seems fine. I have spent all day working on this problem. I have read several posts about similar issues on stackoverflow, I even watched a video on youtube. I am calling everything from the main UI thread.
I can work around this problem by restarting my activity, but I want to do it better than that. I have tried invalidate and such things, but nothing helped. Please note the VERY STRANGE thing, I wrote...
I think, that I have to force the activity to redraw its views, or something like that, but even refreshDrawableState(); on my listview did not help.
Thank you in advance!
Basically If you want to clear list data then just call
newsArrayList.clear();
lv.setAdapter(null);
Again add what ever your data in ArrayList and set adapter
newsArrayList = new ArrayList<NewsClass>();
Adapter = new NewsAdapter(this, newsArrayList); // considering list with some elements
lv.setAdapter(Adapter);
If you are getting outofmemory error/exception then there is problem with your Adapter Class inside getView method

Strange ListView behaviour on data set change

In my application I'm using number of items checked on list.
I'm also updating items on list, deleting them and inserting new ones.
Case scenario:
I'm removing item from list used by adapter
I'm calling notifyDataSetChange
I'm refreshing my component
My component is asking mMyListView for number of checked items
View is returning old number
I was browsing android sources and find out that even if I call notifyDataSetChange, handleDataChanged method (some package-protected AbsListView method) is not call immediately, but later. And this is the function responsible for changing number of checked items.
I resolved it by extending ListView with my own class with only one change:
#Override
public long[] getCheckedItemIds() {
handleDataChanged();
return super.getCheckedItemIds();
}
To get checked items number, I'm calling mMyListView.getCheckedItemIds().length (because of API level I cannot use getCheckedItemCount).
Now it works, but I don't like this solution. Am I missing something here?

Categories

Resources