Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - setVisibility results in java.util.ConcurrentModificationException

I am hiding a view via setVisibility(View.INVISIBLE). Later on when I try to show the view again in a different method via setVisibility(View.VISIBLE) I get the following exception

03-28 01:32:05.450: E/AndroidRuntime(20895): FATAL EXCEPTION: main
03-28 01:32:05.450: E/AndroidRuntime(20895): java.util.ConcurrentModificationException
03-28 01:32:05.450: E/AndroidRuntime(20895): at java.util.HashMap$HashIterator.nextEntry(HashMap.java:796)
03-28 01:32:05.450: E/AndroidRuntime(20895): at java.util.HashMap$KeyIterator.next(HashMap.java:823)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:946)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:948)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:948)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:948)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:948)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewGroup.dispatchDragEvent(ViewGroup.java:948)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewRoot.handleDragEvent(ViewRoot.java:3027)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.view.ViewRoot.handleMessage(ViewRoot.java:2185)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.os.Handler.dispatchMessage(Handler.java:99)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.os.Looper.loop(Looper.java:132)
03-28 01:32:05.450: E/AndroidRuntime(20895): at android.app.ActivityThread.main(ActivityThread.java:4028)
03-28 01:32:05.450: E/AndroidRuntime(20895): at java.lang.reflect.Method.invokeNative(Native Method)
03-28 01:32:05.450: E/AndroidRuntime(20895): at java.lang.reflect.Method.invoke(Method.java:491) 
03-28 01:32:05.450: E/AndroidRuntime(20895): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
03-28 01:32:05.450: E/AndroidRuntime(20895): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
03-28 01:32:05.450: E/AndroidRuntime(20895): at dalvik.system.NativeStart.main(Native Method)

When I comment out the line that changes the visibility back to visible, I don't get the exception.

I first thought that the exception would be caused by some other code iterating through a hashmap, however, I don't do any modifications while iterating through the hashmaps I use, neither do I have multithreading, which seem to be the most common reason for this exception. Also I don't get the exception when I don't change back the visibility.

EDIT:
The exception occurs in a custom fragment. Below is the code where I iterate over the hashmap (mWidgetConfig) that contains information about the configuration of custom widgets that I am trying to restore. The hashmap is a public variable in the fragment.

In an OnDragListener which is created by the fragment, I update the hashmap according to a certain drag operation, like this:

// Update the widget configuration of the fragment that created this listener
                mFragment.mWidgetConfig.put(startCircleTag, "0");

I also iterate over the hashmap to check a certain condition but I don't do any modification during the iteration:

Iterator<String> keySetItr = mFragment.mWidgetConfig.keySet().iterator();
        while(keySetItr.hasNext()) {
            String tag = keySetItr.next();
            if(mFragment.mWidgetConfig.get(tag).equals((String) destSocket.getTag())) {
                // do something, though no modification of the hashmap
                break;

            }
        }

In addition I do one iteration in the fragment itself while trying to restore the widget configuration. Below is the code I use to configure the widget according to the hashmap:

    public void configureWidgets() {
    resetWidgets();

    Iterator<String> keySetItr = mWidgetConfig.keySet().iterator();
    while(keySetItr.hasNext()) {
        String tag = keySetItr.next();
        Integer value = Integer.parseInt(mWidgetConfig.get(tag));

        ImageView destSocket = null;
        switch(value) {
        case 0:
            // The circle will not be connected to any socket
            continue;
        case 1:
            destSocket = mSocket1;
            break;
        case 2:
            destSocket = mSocket2;
            break;
        case 3:
            destSocket = mSocket3;
            break;
        }

        ImageView startCircle = (ImageView) mLayout.findViewWithTag(tag);
        ImageView startPlug = (ImageView) mLayout.findViewWithTag(tag + "_plug");

        // Replace the drawable of destSocket
        destSocket.setBackgroundDrawable(getActivity().getResources().getDrawable(R.drawable.socket_plugged));

        // Hide plug view
        startPlug.setVisibility(View.INVISIBLE);

        // Draw a line between the start circle view and the destination socket view
        mConnectionLinesView.addLine(startCircle, destSocket);
    }
}


public void resetWidgets() {
    // Remove all lines
    mConnectionLinesView.removeLines();

    // Show all eventually previously hidden plugs
    //mPlug1.setVisibility(View.VISIBLE);
    //mPlug2.setVisibility(View.VISIBLE);
    //mPlug3.setVisibility(View.VISIBLE);

    // Set to backround drawable of the socket to the initial one
    mSocket1.setBackgroundDrawable(getActivity().getResources().getDrawable(R.drawable.socket).mutate());
    mSocket2.setBackgroundDrawable(getActivity().getResources().getDrawable(R.drawable.socket).mutate());
    mSocket3.setBackgroundDrawable(getActivity().getResources().getDrawable(R.drawable.socket).mutate());
}

As soon as the lines that set the visibility of the "plugs" above are used in the code, I get the exception.

SOLUTION
The reason the exception got thrown is that I called the configuration methods in the DragEvent.ACTION_DRAG_ENDED case statement of the OnDragListener. When I put the same code into the DragEvent.ACTION_DROP case statement the exception doesn't get thrown. No clue why. Thanks for your help guys

like image 860
Schnodahipfe Avatar asked Mar 27 '12 23:03

Schnodahipfe


1 Answers

As I understand this is caused by ViewGroup implementation details. And it's not connected with multithreading.

When drag starts ViewGroup creates a HashSet of child views that must be notified about ACTION_DRAG_ENDED event. This is a set of visible children. When child visibility is changed a corresponding ViewGroup modifies that set (adding a child if its visibility is VISIBLE). And in your case it happens during iterations over that collection.

Think, the easiest solution for you is to postpone the visibility change.

view.post(new Runnable() {
  public void run() {
    view.setVisibility(View.VISIBLE);
  }
});

And exception does not happen when you put your code into the ACTION_DROP case statement because that set is not being iterated at the moment you change the view visibility.

For details see ViewGroup.dispatchDragEvent(DragEvent) source code.

like image 56
Roman Mazur Avatar answered Nov 14 '22 02:11

Roman Mazur