11

I have crashes in prod that I can't reproduce. The trace does not say where it crashes. After making some searches it looks like it could be related to notifyDataSetChanged(). It happens on Android 6 and 7.

I have 2 packages where I use listview:

A BroadcastReceiver to check the list of wifi APs: in this case the code collects all the APs, adds them in a List and calls notifyDataSetChanged:

 @Override
    public void onReceive(Context context, Intent intent) {
        if (wifiManager != null) {
            if (wifiManager.isWifiEnabled()) {
                List<ScanResult> listeScan = wifiManager.getScanResults();
                listeWifiItem.clear();
                for (ScanResult scanResult : listeScan) {
                    WifiItem item = new WifiItem();

                    item.setAdresseMac(scanResult.BSSID);
                    item.setAPName(scanResult.SSID);
                    item.setForceSignal(scanResult.level);

                    listeWifiItem.add(item);
                }

                wifiAdapter.notifyDataSetChanged();
            } else {
                Toast.makeText(context, "You must activate the WiFi", Toast.LENGTH_SHORT);
            }
        }
    }

And an async task in which I call notifyDataSetChanged in the method onPostExecute:

@Override
protected void onPostExecute(Void result) {
    iPadaptater.notifyDataSetChanged();
    super.onPostExecute(result);
}

Is there an issue with the way I use notifyDataSetChanged? In the async task, if the user moves to another function, I thought it was safer to do it

Do you think the crash is related to notifyDataSetChanged?

Do you see another important info from the traces?

Here are the 2 types of traces:

-type 1:

  java.lang.IllegalStateException: 
      at android.widget.ListView.layoutChildren (ListView.java:1747)
      at android.widget.AbsListView$CheckForTap.run (AbsListView.java:4728)
      at android.os.Handler.handleCallback (Handler.java:751)
      at android.os.Handler.dispatchMessage (Handler.java:95)
      at android.os.Looper.loop (Looper.java:154)
      at android.app.ActivityThread.main (ActivityThread.java:6776)
      at java.lang.reflect.Method.invoke (Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:1520)
      at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1410)

-type 2:

java.lang.IllegalStateException: 
  at android.widget.ListView.layoutChildren (ListView.java:1623)
  at android.widget.AbsListView.onTouchUp (AbsListView.java:4210)
  at android.widget.AbsListView.onTouchEvent (AbsListView.java:3969)
  at android.view.View.dispatchTouchEvent (View.java:9957)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2705)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2386)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:2711)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2400)
  at com.android.internal.policy.DecorView.superDispatchTouchEvent (DecorView.java:416)
  at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent (PhoneWindow.java:1837)
  at android.app.Activity.dispatchTouchEvent (Activity.java:3154)
  at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent (WindowCallbackWrapper.java:63)
  at com.android.internal.policy.DecorView.dispatchTouchEvent (DecorView.java:378)
  at android.view.View.dispatchPointerEvent (View.java:10177)
  at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent (ViewRootImpl.java:4634)
  at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess (ViewRootImpl.java:4502)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:3953)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:4006)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:3972)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:3980)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:3953)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:4006)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:3972)
  at android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:4101)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:3980)
  at android.view.ViewRootImpl$AsyncInputStage.apply (ViewRootImpl.java:4158)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:3953)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:4006)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:3972)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:3980)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:3953)
  at android.view.ViewRootImpl.deliverInputEvent (ViewRootImpl.java:6443)
  at android.view.ViewRootImpl.doProcessInputEvents (ViewRootImpl.java:6417)
  at android.view.ViewRootImpl.enqueueInputEvent (ViewRootImpl.java:6378)
  at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent (ViewRootImpl.java:6577)
  at android.view.InputEventReceiver.dispatchInputEvent (InputEventReceiver.java:185)
  at android.os.MessageQueue.nativePollOnce (Native Method)
  at android.os.MessageQueue.next (MessageQueue.java:323)
  at android.os.Looper.loop (Looper.java:136)
  at android.app.ActivityThread.main (ActivityThread.java:6311)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:872)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:762)
narb
  • 958
  • 1
  • 13
  • 39
  • Do you perform `listeWifiItem.add(item)` on a background thread? – azizbekian Oct 12 '17 at 02:55
  • My app uses multiple listview and as the traces don't say much I'm trying to summarize the 2 scenarios where I use listview. I have updated the code above about wifi. And to respond to your question this particular case of the wifi doesn't run in a background thread. it's a broadcastreceiver. What should I look for? in the scenario where I use an async task I usually add all the items in OnProgressUpdate or onPostExecute and then call notifyDataSetChanged. I have very few crash but still it's annoying. – narb Oct 12 '17 at 15:27

2 Answers2

8

Looking at the source code for API 25, the only place that an illegal state exception is thrown is in LayoutChildren() of ListView. The exception states in the code:

Extract from ListView$LayoutChildren:

// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
    resetList();
    invokeOnItemScrollListener();
    return;
} else if (mItemCount != mAdapter.getCount()) {
    throw new 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. Make sure your adapter calls notifyDataSetChanged() "
            + "when its content changes. [in ListView(" + getId() + ", " + getClass()
            + ") with Adapter(" + mAdapter.getClass() + ")]");
}

This indicates that there is a mismatch between the internal item count maintained by ListView and the item count maintained by the adapter. It looks like LayoutChildren is probably being called from the CheckforTap inner class of AbsListView:

Extract from AbsListView$CheckforTap:

if (!mDataChanged) {
    final float[] point = mTmpPoint;
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    child.drawableHotspotChanged(point[0], point[1]);
    child.setPressed(true);
    setPressed(true);
    layoutChildren();
    // ... and it continues...

This is probably why others are asking if you are changing the data set from a non-UI thread. It also seems to point to a potential issue with how you are calling notifyDataSetChanged().

Assuming that you are using the UI thread for the changes and without knowing more about the internal workings of your app, I can just suggest that you not separate the call to notifyDataSetChanged() from the changes that you are making. You appear to do so from your responses.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Is it normal that I don't get the message "The content of the adapter...." in the trace? How can it happen that I get no info on where it crashed in the traces? where did you get the src code (I have a couple more crashes where I have no info in the traces;)). Many thanks for the info. I'll review one more time my code and will get back to you. – narb Oct 17 '17 at 05:24
  • @narb I can't explain why you don't get the message but that is the only place that exception is thrown and it is very close to the line number in the error message. I feel confident that it is the error you are seeing. As for the source, type ctrl-B (Windows) on "ListView" in your source. That will take you the bytecode decompile, then at the top click "download source" or something to that effect. Also you may be able to follow [these instructions](https://source.android.com/source/downloading). – Cheticamp Oct 17 '17 at 10:24
  • @narb I am curious if you found the issue in your code. – Cheticamp Oct 17 '17 at 15:39
  • only time will tell as it only happens at subscribers of the app :) but I found a couple of weird calls to notifyDataSetChanged(). When you have no indications in the trace where it crashed it is painful. I still don't understand why android is not giving more infos. I have still a couple like that: tgkill and a getColorStateList problem. writing highly multi processed apps is not ez. – narb Oct 17 '17 at 16:24
  • Hi Cheticamp, sorry to bug you. My issue is still there. To summarize: I never remove entries to recreate them. What I do is create the listview, if I need to change something I clear() the List item and then recreate the listview. So my understanding is: the 1st I create the listview I don't have to call notifydatasetchanged. I just do item.setxxx() listitem.add() and listview.setadapter(adapter). The only case when I have to call notifydatasetchanged is right after after a listitem.clear(). Agree? – narb Nov 13 '17 at 15:17
  • @narb What you say sounds right. It still looks to me like there is an issue with notifying the `ListView` of changes. The broadcast receiver should be running on the UI thread unless you explicitly have it running on another thread which, I assume, you don't. Is there a race condition? You might consider making a logcat entry at each point you change the dataset and each time you notify the adapter of a change. Those entries along with the logcat trace may help you narrow down where the error is occurring. – Cheticamp Nov 13 '17 at 15:34
  • The app is a drawer. Almost the first thing I do is to call the bdcast receiver. Then I move to fragments depending on what the user does. So you could say that the bdcast rec doesn't run in the UI? Is it bad? Concerning logcat, the big pb is the app is running like a charm most of the time: I never had a crash like that + I have several k users but yesterday for ex, I got 4 crashes. And I tried heavily to simulate users' behavior: stop wifi, stop 3/4g, re-enable network, etc... – narb Nov 13 '17 at 16:06
  • @narb It should be easy to check to see if the receiver or any other code runs on the UI thread or not. See [this](https://stackoverflow.com/a/7897562/6287910) on how to do it. – Cheticamp Nov 13 '17 at 17:50
  • UI :) I found that each time I was doing a list clear I did not have a notifydatasetchanged. That's maybe the issue. Let's see. A last question: if it's the first time I create the listview and I call notifydatasetchanged it's not necessary but not forbidden? agree? – narb Nov 13 '17 at 18:18
  • @narb Sounds like that may be the issue. I don't think an extra call to notify will hurt. – Cheticamp Nov 13 '17 at 18:21
  • we'll see :) Your support was much appreciated. Thanks! – narb Nov 13 '17 at 18:39
0

You need to add items to your listeWifiItem and called notifyDataSetChanged() in the UI thread.

@Override 
protected void onPostExecute (Void result){
            super.onPostExecute(result);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    listeWifiItem.clear();
                    for (ScanResult scanResult : listeScan) {
                        WifiItem item = new WifiItem();

                        item.setAdresseMac(scanResult.BSSID);
                        item.setAPName(scanResult.SSID);
                        item.setForceSignal(scanResult.level);

                        listeWifiItem.add(item);
                    }
                    wifiAdapter.notifyDataSetChanged();
                }
            });
        }
THANN Phearum
  • 1,969
  • 22
  • 19
  • I apologize my email was not completely clear but IMHO this is not the issue . see the comment to azizbekian – narb Oct 12 '17 at 15:29