Android: Modifying adapter content from a background thread - android

I've read up quite a bit on the exception thrown while using BaseAdapter, but i can't seem to find an alternative solution:
Make sure the content of your adapter is not modified from a background thread
What i want to achieve is to keep a copy of a message queue in memory, and use this message queue to populate the BaseAdapter for my ListView. The reason im doing this is that the message queue will keep getting messages from a socket even when the ListView is not currently present (for example a chat window).
The problem comes when i have the Activity with the ListView in foreground, BaseAdapter binded to the message queue's data, and a message comes in the socket. Adding the new message into the queue will throw the exception mentioned above. Unless i pre-populate my BaseAdapter with the message queue (as in the BaseAdapter having its own message queue) and updating both of them when a new message come in, i can't really find a way around this issue.
I don't really want to double up the effort on keeping those 2 queues up-to-date like this, surely there is a better way of doing this? Send broadcasts around doesn't work either because of the potential delay in the adapter serving a scroll and the notifyDataSetChanged call is made.

Use a Handler to modify the "message queue" from the main application thread.

I had this problem as well. It turns out I was recreating my adapter for new data but the old adapter was still set to the listview and would occasionally cause a crash. So I do something like this when I need to recreate the adapter:
//Clear adapter's array
if (!array.isEmpty()) { array.clear(); }
if (adapter != null) { adapter = null; }
setListAdapter(adapter);
//Later code
//array gets repopulated
//adapter gets recreated
setListAdapter(adapter);

Related

Android realm findAllAsync() freezing UI

when I'm querying lot of elements the UI get freeze.
realm.where(TVRealm.class).equalTo("favorite", true).findAllAsync()
.addChangeListener(new RealmChangeListener<RealmResults<TVRealm>>() {
#Override
public void onChange(RealmResults<TVRealm> element) {
List<TV> tvList = new ArrayList<>();
for (TVRealm tvRealm : element) {
tvList.add(prepareTV(TVRealm.toTV(tvRealm), true));
}
listener.onDataChange(tvList);
}
});
I thought that findAllAsync() will run on other thread and avoid the issue but not.
Does anyone knows how to avoid this issue? Maybe there is another way without using findAllAsync() method.
Thanks.
According to official Realm document, findAllAsync works in a background thread.
https://realm.io/docs/java/latest/#asynchronous-queries
I think your data changes too often and so you're trying to notify ui too often. So you're blocking ui. I guess you are notifying a RecyclerView adapter in your onDataChange method.
Also if your result list has too many items, every time when your data changed exploring the results and adding items to a new list may block ui.
If I don't make mistake, while RealmResults is updating, for each change you create new collection with new models and update UI. Try to call findAll() in another thread, map results to TVs and post completed list of TVs to UI thread.
why dont you try this
RealmResult<item> res=realm.where(item.class).where("name","sanjay").findAll();
it will get all the data at a time.

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

What is more expensive? Creating a new ArrayAdapter every time the data changes or updating an existing ArrayAdapter?

To update a Listview we can do following two things:
1) Create a new ArrayAdapter and bind it to ListView everytime the data is updated.
this.listView.setAdapter(new ArrayAdapter(getActivity(), data));
or
2) Update the contents of ArrayAdapter which is already binded to listview:
this.arrayadapter.clear();
for (Data item : data)
this.arrayadapter.add(item);
this.arrayadapter.notifyDataSetChanged();
So which method is more expensive?
The way you are doing it, the second one is definitely more expensive. ArrayAdapter calls notifyDataSetChanged() internally in clear() as well as in add(). You could change it to
this.arrayadapter.setNotifyOnChange(false);
this.arrayadapter.clear();
for (Data item : data)
this.arrayadapter.add(item);
this.arrayadapter.setNotifyOnChange(true);
this.arrayadapter.notifyDataSetChanged();
But even if you it this way, both calls contain a synchronized block which makes them slow. So even though you are creating a new ArrayAdapter in the first method, I would say it is faster.
You could further optimize it by using a custom adapter with a setItems(data) method that just replaces the internal data list.
I'd becareful with the accepted answers final conclusion. While SimonSays makes valid points about the iteration and adding...there's a lot that happens with setting a new adapter he fails to recognize. Which makes it hard to say if it's actually any better then the suggested for...each approach.
Setting a new adapter will cause the ListView to flush out all it's recycled view's. It'll also have to re-measure all the new views coming in...which leads to the getView() being called 3-4 times per item for several of the positions...if not all positions. (Varies with platform). So even though there's a sync block on adding, you'll probably see the getView method invoked far less times with possibility of using recycled views.
Basically, knowing which is better is hard and depends greatly on use case. I'd vote to stick with the for...each approach simply for better readability.

Live Update ListView

I have dug deep down into SO but, although I have found other people asking similar questions to mine, I have not yet find a question that addresses the same issues I have. I have not found a satisfying answer either.
I have a ListView. When I call from the adapter, .notifyDataSetChanged, the ListView is updated, but I can see the update only once onResume() is called. In other words, I do not see it instantly, only after I leave the activity and comeback.
What can I do to see the update instantly? I have tried the .notifyDataSetChanged method, I have tried resetting the adapter... nothing worked.
According to your comment, you dont update the array IN the adapter, but an array held by the activity you passed to the adapter once. Thats why the adapter isnt updating properly. You are changing the array outside of your adapter-class, which might not be the same array-object your adapter is using. At onResume(), your adapter is recreated with the new array and showing the new content.
A solution would be using the following custom Adapter class:
class MyAdapter extends BaseAdapter {
private Array[] myArray;
public MyAdapter(Array[] myArray) {
this.myArray = myArray;
}
public updateContent(Array[] myNewArray) {
this.myArray = myNewArray;
this.notifyDataSetChanged();
}
// your getItem, getView, and so on methods
}
Then from your activity, simple call myArray.updateContent() with your new Array and it will update immediatly.
Its never good to hold and manipulate an object used from one class (the adapter) within another one (the activity). Try to move all code for manipulating the array into the adapter and use methods to add/remove items. This will make it a lot easier finding this kind of errors!

notifyDataSetChanged() does not update the listview

I have a list of 4 items, I have used listview. I want to change a string dynamically on recieving internal event. I see that when I receive the event I am setting the string correctly but and then calling
mAdapter.notifyDataSetInvalidated();
mAdapter.notifyDataSetChanged();
but the list is not getting updated.
I've had the same experience. The cause was that the list adapter was updating on the wrong thread i.e. not the UI thread. This is easily solved by changing the adapter data on the UI thread through (as I found on other posts):
runOnUiThread(new Runnable() {
public void run() {
// code that changes the list adapter data
}
});
Of course you can always create a (inner) class that implements Runnable that is provided with the list adapter and data to add, insert etc.
Note: calling notifyDataSetInvalidated() or notifyDataSetChanged() will not be necessary as it is called by default, unless you turned it off explicitly with setNotifyOnChange(false);
I think that notifyDataSetChanged only works if you use the add (or insert), remove or clear functions on adapter.
You can rebuilt the list adapter for force to refresh the listView.
Excuse me for my bad english

Categories

Resources