microg/GmsCore · error · IllegalStateException

Cannot remove OnSwitchChangeListener

Error message

Cannot remove OnSwitchChangeListener

What it means

Listener-contract guard in SwitchBar.removeOnSwitchChangeListener: the supplied listener is not currently registered, so there is nothing to remove — an unbalanced remove call (or a listener that was never added).

Source

Thrown at play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java:192

            mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
        }
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        propagateChecked(isChecked);
    }

    public void addOnSwitchChangeListener(OnSwitchChangeListener listener) {
        if (mSwitchChangeListeners.contains(listener)) {
            throw new IllegalStateException("Cannot add twice the same OnSwitchChangeListener");
        }
        mSwitchChangeListeners.add(listener);
    }

    public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) {
        if (!mSwitchChangeListeners.contains(listener)) {
            throw new IllegalStateException("Cannot remove OnSwitchChangeListener");
        }
        mSwitchChangeListeners.remove(listener);
    }

    static class SavedState extends BaseSavedState {
        boolean checked;
        boolean visible;

        SavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            checked = (Boolean) in.readValue(Boolean.class.getClassLoader());

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Only call removeOnSwitchChangeListener for listeners you know were added; track registration state yourself.
  2. Guard removal with a flag or check before calling.
  3. Make add/remove strictly paired in symmetric lifecycle callbacks (onCreate/onDestroy).
  4. Wrap in a null/empty check if the SwitchBar may have been torn down earlier.

Example fix

// before
switchBar.removeOnSwitchChangeListener(this);
// after
if (listenerRegistered) {
    switchBar.removeOnSwitchChangeListener(this);
    listenerRegistered = false;
}
Defensive patterns

Strategy: validation

Validate before calling

if (listenerRegistered) switchBar.removeOnSwitchChangeListener(listener);

Try / catch

try { switchBar.removeOnSwitchChangeListener(listener); } catch (IllegalStateException e) { // not registered; ignore }

Prevention

When it happens

Trigger: Calling removeOnSwitchChangeListener(listener) for a listener that was never added, or that was already removed (e.g. double invocation of a cleanup path, or cleanup running before setup).

Common situations: Calling remove in onDestroy when onCreate setup never ran (activity finished early); calling remove twice from two lifecycle paths; removing a different listener instance than the one added.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/42a742f2137eb184. Report an issue: GitHub.