Home » Android » Android – setVisibility results in java.util.ConcurrentModificationException

Android – setVisibility results in java.util.ConcurrentModificationException

Posted by: admin June 15, 2020 Leave a comment

Questions:

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

How to&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.

Answer:

Try using:

setVisibility(View.GONE);
setVisibility(View.VISIBLE);

Answer:

Another possible solution is to wrap your draggable view in some ViewGroup (e.g. FrameLayout) and keep that visible all the time.

This way only the draggable view contained within will disappear leaving a hole where it was (just as before), but the parent of the wrapper won’t get notified of the draggable being hidden.

Esentially go from

ViewGroup:parent
 ┗ View:draggable (toggling setVisible on this one, parent gets notified)

to

ViewGroup:parent
 ┗ ViewGroup:wrapper (setVisible never called on this one)
     ┗ View:draggable (toggling setVisible on this one, wrapper gets notified)

The ConcurrentModificationException is avoided here because the problematic Map only contains one element, hence one iteration is done, which means one call to Iterator.hasNext/.next.

Decide for yourself if this is a hack 🙂

Note: your edit has nothing to do with the question, because the exception is about ViewGroup.mDragNotifiedChildren and not your Map (see stacktrace),