Tencent/QMUI_Android · error · RuntimeException

Can not add skinChangeListener while dispatching

Error message

Can not add skinChangeListener while dispatching

What it means

QMUISkinManager.addSkinChangeListener() refuses to add a listener while the manager is in the middle of dispatching a skin change (mIsInSkinChangeDispatch == true). Allowing re-entrant listener mutation during dispatch would cause ConcurrentModificationException or inconsistent notification, so it throws a RuntimeException instead.

Source

Thrown at qmui/src/main/java/com/qmuiteam/qmui/skin/QMUISkinManager.java:641

                } else if (item instanceof Window) {
                    dispatch(((Window) item).getDecorView(), index);
                } else if (item instanceof View) {
                    dispatch((View) item, index);
                }
            }
        }

        for (int i = mSkinChangeListeners.size() - 1; i >= 0; i--) {
            OnSkinChangeListener item = mSkinChangeListeners.get(i);
            item.onSkinChange(this, oldIndex, mCurrentSkin);
        }
        mIsInSkinChangeDispatch = false;
    }

    @MainThread
    public void addSkinChangeListener(@NonNull OnSkinChangeListener listener) {
        if (mIsInSkinChangeDispatch) {
            throw new RuntimeException("Can not add skinChangeListener while dispatching");
        }
        mSkinChangeListeners.add(listener);
    }

    public void removeSkinChangeListener(@NonNull OnSkinChangeListener listener) {
        if (mIsInSkinChangeDispatch) {
            throw new RuntimeException("Can not add skinChangeListener while dispatching");
        }
        mSkinChangeListeners.remove(listener);
    }

    public int getCurrentSkin() {
        return mCurrentSkin;
    }

    public interface OnSkinChangeListener {
        void onSkinChange(QMUISkinManager skinManager, int oldSkin, int newSkin);
    }

View on GitHub (pinned to 026e7d4866)

Solutions

  1. Defer the addSkinChangeListener call until after dispatch, e.g. view.post(() -> manager.addSkinChangeListener(l)).
  2. Register all listeners during initialization, before any changeSkin call.
  3. In the listener callback, post the registration to the main queue instead of calling it synchronously.

Example fix

// before
manager.addSkinChangeListener((manager2, old, now) -> {
    manager2.addSkinChangeListener(analyticsListener); // throws: inside dispatch
});

// after
manager.addSkinChangeListener((manager2, old, now) -> {
    view.post(() -> manager2.addSkinChangeListener(analyticsListener));
});
Defensive patterns

Strategy: try-catch

Try / catch

try {
    skinManager.addSkinChangeListener(listener);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("while dispatching")) {
        new Handler(Looper.getMainLooper()).post(() -> skinManager.addSkinChangeListener(listener));
    } else throw e;
}

Prevention

When it happens

Trigger: Calling addSkinChangeListener() from inside an OnSkinChangeListener callback or from skin-refresh code (e.g. a view's skin handler) that runs synchronously during changeSkin dispatch.

Common situations: A listener implementation that lazily registers another listener; skin listener trying to register an analytics listener triggered by the change event itself.

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 Tencent/QMUI_Android@026e7d4866 (2026-09-06). Data as JSON: /api/errors/3250ec105d9f5dd4. Report an issue: GitHub.