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
- Defer the addSkinChangeListener call until after dispatch, e.g. view.post(() -> manager.addSkinChangeListener(l)).
- Register all listeners during initialization, before any changeSkin call.
- 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
- Register all OnSkinChangeListener instances during initialization, never inside callbacks
- Defer registrations made in response to skin changes via view.post or Handler
- Avoid adding listeners from custom views' skin-refresh handlers
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
- index must greater than 0
- already exist the theme item for
- The skin does not exist
- Target already exists other parent view.
- Action view already exists other parent view.
AI-assisted analysis of Tencent/QMUI_Android@026e7d4866 (2026-09-06).
Data as JSON: /api/errors/3250ec105d9f5dd4.
Report an issue: GitHub.