When I receive a lot of push messages (let's say 50) from GCM within 1 second I receive the following exception:
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131427434, class android.widget.ListView) with Adapter(class a.n)] at android.widget.ListView.layoutChildren(ListView.java:1544) at android.widget.AbsListView.onLayout(AbsListView.java:2045) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v4.view.ViewPager.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v4.widget.DrawerLayout.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1998) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1812) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1050) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4560) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749) at android.view.Choreographer.doCallbacks(Choreographer.java:562) at android.view.Choreographer.doFrame(Choreographer.java:532) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735) at android.os.Handler.handleCallback(Handler.java:725) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:5171) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:564) at dalvik.system.NativeStart.main(Native Method)
I have already tried to fix this, by putting BOTH the messages.add()
and notifyDataSetChanged()
inside the runOnUIThread
. I guess that happens because onUpdate()
of my listener is called for every push message. But shouldn't that problem be solved by runOnUIThread()
, because everything is executed consecutively?
MainApplication app = (MainApplication) context.getApplicationContext();
app.setOnRoomMessageUpdateListener(new OnRoomMessageUpdateListener() {
@Override
public void onUpdate() {
// save message with highest time, so we can only query the new
// messages
long highestTime = getHighestMessageTime();
messageDatabase.getConditionBuilder().add(
DatabaseHelper.KEY_MESSAGE_ROOM_ID + " = ? AND " + DatabaseHelper.KEY_MESSAGE_LOCAL_TIME
+ " > ? AND " + DatabaseHelper.MESSAGE_TABLE_NAME + "."
+ DatabaseHelper.KEY_MESSAGE_USER_ID + " <> ?",
new String[] { String.valueOf(roomID), String.valueOf(highestTime),
String.valueOf(user.getUserID()) });
messageDatabase.getConditionBuilder().setSortOrder(DatabaseHelper.KEY_MESSAGE_LOCAL_TIME + " DESC");
final ArrayList<Message> newMessages = messageDatabase.getList();
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
messages.addAll(newMessages);
messageAdapter.notifyDataSetChanged();
}
});
}
});
EDIT: I probably missed out a very important part of the code, which I overlooked myself:
app.setOnRoomUserUpdateListener(new OnRoomUserUpdateListener() {
@Override
public void onUpdate(final User user, final int roomID, final int joinStatus) {
final String message;
if (joinStatus == OnRoomUserUpdateListener.USER_JOINED) {
message = context.getString(R.string.join_room_message, user.getUsername());
} else {
message = context.getString(R.string.leave_room_message, user.getUsername());
}
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
messages.add(new Message(-1, user, message, System.currentTimeMillis(), System
.currentTimeMillis(), false, roomID, 0, true, Message.TYPE_JOINLEAVE));
messageAdapter.notifyDataSetChanged();
}
});
}
});
This code block is located below the code above and it's obviously also modifying the content of the adapter. When both of them run simultaneous, there could be a problem, right? Could this be fixed by using synchronized
or is there a better way?
EDIT 2: Initialization:
// get all messages
messages = new ArrayList<Message>();
messageDatabase.getConditionBuilder().add(DatabaseHelper.KEY_MESSAGE_ROOM_ID + " = ?",
new String[] { String.valueOf(roomID) });
messageDatabase.getConditionBuilder().setSortOrder(DatabaseHelper.KEY_MESSAGE_LOCAL_TIME + " DESC");
messageDatabase.getConditionBuilder().setSqlLimit(100);
messages.addAll(messageDatabase.getList());
// get "user joined/left" messages
UserDatabase userDatabase = UserDatabase.getInstance(context);
messages.addAll(userDatabase.getJoinLeaveMessages(roomID));
Collections.sort(messages);
messageAdapter = new MessageAdapter(getActivity(), R.layout.list_message_item, messages);
listView.setAdapter(messageAdapter);
Edit 3: Full source of the fragment, which contains the code: https://gist.github.com/ChristopherWalz/89a071b1606460e18ce7
messages.add()
(as opposed tomessageAdapter.add()
) - modifyingAdapter
's data externally to theAdapter
is a bad practice. Are there any other places in the code wheremessages
gets modified? – Darellmessages.add()
is called three times: Two parts are posted in my question, the other part is when writing a message on your own. But this should not be relevant, because the exception is happening when turning of the internet, received ~50 push messages and turning the internet on again, so all the push messages are received within 1 second. – Boathousesynchronized
? How do other apps which receive a lot of push messages handle them? I guess I'm not the only one with this kind of problem. – Boathousemessages
to yourmessageAdapter
? – Archbishopric