Search code examples
androidandroid-viewpagerupnpfragmentpageradapterandroid-pageradapter

Android viewpager notifyDataSetChanged causes IllegalStateException


Using cling library for upnp discovery. Whenever deviceDiscovered fires up, calling the below code to update device list and hence forth, calling notifyDataSetChanged of ViewPager

public void deviceAdded(Registry registry, Device device) {
        // TODO Auto-generated method stub
        super.deviceAdded(registry, device);

        String name = device.getDisplayString();
        String manufac = device.getDetails().getManufacturerDetails()
                .getManufacturer();
        if (!manufac.contains("Microsoft")) {
            if (!deviceNames.contains(name)) {
                if (!devices.contains(device)) {
                    devices.add(device);
                    deviceNames.add(name);
                    Log.e(TAG,"\t\tdevice.size() = "+String.valueOf(devices.size()));
                //  Log.e(TAG,"Calling notify runnable after adding device "+deviceNames.get(deviceNames.size()-1));
                    runOnUiThread(notifyAdapterDataChanged);
                }

            }
        }
}

Field declarations

ArrayList/*CopyOnWriteArrayList*/<Device> devices;
    ArrayList/*CopyOnWriteArrayList*/<String> deviceNames;

notifyAdapterDataChanged is the runnable, as below

@Override
        public void run() {
            if(adapter != null){
                adapter.notifyDataSetChanged();
             }
       }

Getting below exception

07-25 02:58:36.185: E/AndroidRuntime(11404): FATAL EXCEPTION: main
07-25 02:58:36.185: E/AndroidRuntime(11404): java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 5, found: 6 Pager id: com.example.upnpclient/myviewpager Pager class: class android.support.v4.view.ViewPager Problematic adapter: class com.example.upnpclient.MyPagerAdapter
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.support.v4.view.ViewPager.populate(ViewPager.java:967)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.support.v4.view.ViewPager.populate(ViewPager.java:919)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1441)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.View.measure(View.java:15473)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.View.measure(View.java:15473)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5056)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.View.measure(View.java:15473)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.LinearLayout.measureVertical(LinearLayout.java:833)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.View.measure(View.java:15473)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5056)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2361)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.View.measure(View.java:15473)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1974)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1217)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1390)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1112)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4472)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.Choreographer.doCallbacks(Choreographer.java:555)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.Choreographer.doFrame(Choreographer.java:525)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.os.Handler.handleCallback(Handler.java:615)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.os.Handler.dispatchMessage(Handler.java:92)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.os.Looper.loop(Looper.java:137)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at android.app.ActivityThread.main(ActivityThread.java:4898)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at java.lang.reflect.Method.invokeNative(Native Method)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at java.lang.reflect.Method.invoke(Method.java:511)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
07-25 02:58:36.185: E/AndroidRuntime(11404):    at dalvik.system.NativeStart.main(Native Method)

Solution

  • First of all: After every time you add an item to the list referenced in an adapter, you have to call notifyDataSetChanged.

    Your situation usually happens when a second item is added before the UI thread is done executing notifyDataChanged. You can prevent this by adding the items using an adapter.

    If you rather want to add the items directly to the list and not to the the adapter, you can use a mutex to wait for runOnUIThread to finish - like this:

    final Semaphore mutex = new Semaphore(0);
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            adapter.notifyDataSetChanged();
            mutex.release();
        }
    });
    
    try {
        // wait for the line mutex.release(); to be executed
        mutex.acquire();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    Now, if you add an item to the list and start off the UI thread, the thread that is adding the item waits (via the mutex) for the UI thread to be done with notifying the adapter. This way the case that an additional item slips into the list before the adapter has been updated will be prevented.

    Edit: Another solution would be adding the items in the UI thread, too. In your case like so:

    runOnUiThread(new Runnable() {
            @Override
            public void run() {
                devices.add(device);
                deviceNames.add(name);
                adapter.notifyDataSetChanged();
            }
        });
    

    This way you are avoiding a mutex while still only adding one item per adapter.notifyDataSetChanged(); call.