I've been struggling with this for some time now. In my project there are several Activities that include a ListView and a custom adapter extending BaseAdapter. They also implement some interfaces which usually inform it that the data has been changed.
However, when I call the notifyDataSetChanged() from the listener/interface method implemented in my class, the ListView is not updated and none of the adapter methods are called (getCount(), getView()).
I know this has something to do with the fact where the notifyDataSetChanged() is called from, I mean which thread, but I don't quite understand it and couldn't find a straightforward explanation of this behavior.
As a workaround of this problem I use a Handler which calls a method periodically and in that method I look if a boolean 'needsToBeUpdated' value is true and I call the notifyDataSetChanged() method then. This is ugly, though, and I am sure there needs to be a way to do it asynchronously.
Any help would be appreciated.
Pseudo-code of what I'm talking about:
public class FriendsActivity extends Activity implements FriendsListener {
private ListView mListView;
private ArrayList<Friends> mFriendsList;
private FriendsAdapter mFriendsAdapter;
private boolean mNeedsToBeUpdated;
private Handler mListUpdateHandler;
private Runnable mListUpdateTask;
onCreate() {
initViews();
mFriendsAdapter = new FriendsAdapter(mFriendsList);
mListView.setAdapter(mFriendsAdapter);
SomeStaticClass.addFriendListener(this)
mNeedsToBeUpdated = false;
mListUpdateHandler = new Handler();
mListUpdateHandler.removeCallbacks(mListUpdateTask);
mListUpdateHandler.postDelayed(mListUpdateTask, 10000);
}
onListenerMethod() {
updateFriendsList();
mFriendsAdapter.updateDataSource(mFriendsList);
mFriendsAdapter.notifyDataSetChanged(); // THIS DOESN'T UPDATE THE VIEWS
mNeedsToBeUpdated = true;
}
protected void onResume() {
mListUpdateTask = new Runnable() {
public void run() {
if (mNeedsToBeUpdated ) {
updateFriendsList();
mFriendsAdapter.updateDataSource(mFriendsList);
mFriendsAdapter.notifyDataSetChanged(); // THIS UPDATES THE VIEWS
mListsRequireUpdating = false;
}
mListUpdateHandler.postDelayed(mListUpdateTask, 10000);
}
};
mListUpdateHandler = new Handler();
mListUpdateHandler.removeCallbacks(mListUpdateTask);
mListUpdateHandler.post(mListUpdateTask);
super.onResume();
}
EDIT: Of course it took me almost no time to find the answer once I posted this here...
I hate to do it but I put some more effort into looking for an answer and I think I found it thanks to this: http://developer.android.com/resources/articles/painless-threading.html - and this: http://developer.android.com/reference/android/os/Handler.html
The solution is painfully simple. It is not possible to change the UI in any other thread but the main/UI thread. That's why doing it in the callback methods doesn't work. However, creating a Handler in the UI Thread (e.g. in the onCreate() method) connects it to the UI Thread and can be later used to post events to this thread.
mListUpdateHandler = new Handler();
Then, an inner class implementing the Runnable interface is needed where the UI tweaking is done.
class UpdateListRunnable implements Runnable {
@Override
public void run() {
Log.i(LOGTAG, "UpdateListRunnable");
FriendsActivity.this.updateLists();
}
}
Finally, in the callback method we post the event with out UpdateListRunnable class to the main thread via the Handler:
@Override
public void entriesUpdated(Collection<String> entries) {
Log.i(LOGTAG, "entriesUpdated");
for (String entry : entries) {
Log.i(LOGTAG, "entry: " + entry);
}
mListUpdateHandler.post(new UpdateListRunnable());
}
Thanks to that the upadteLists() method is run in the UI thread and everything works like a charm.