Tencent/QMUI_Android · error · IllegalAccessError

don't call #onEnterAnimationEnd() directly

Error message

don't call #onEnterAnimationEnd() directly

What it means

QMUIFragment.onEnterAnimationEnd() is a framework-dispatched lifecycle callback run when the enter animation finishes; it flips mEnterAnimationStatus to END, updates isInEnterAnimationLiveData to false and runs delayed render runnables. Like its Start counterpart, a mCalled guard throws IllegalAccessError when it is invoked directly, because running it twice would prematurely clear animation state and re-run delayed renderables.

Source

Thrown at arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java:1408

     * may not be call.
     * @param animation
     */
    protected void onEnterAnimationStart(@Nullable Animator animation) {
        if (mCalled) {
            throw new IllegalAccessError("don't call #onEnterAnimationStart() directly");
        }
        mCalled = true;
        mEnterAnimationStatus = ANIMATION_ENTER_STATUS_STARTED;
        isInEnterAnimationLiveData.setValue(true);
    }

    /**
     * may not be call.
     * @param animation
     */
    protected void onEnterAnimationEnd(@Nullable Animator animation) {
        if (mCalled) {
            throw new IllegalAccessError("don't call #onEnterAnimationEnd() directly");
        }
        mCalled = true;
        mEnterAnimationStatus = ANIMATION_ENTER_STATUS_END;
        isInEnterAnimationLiveData.setValue(false);
        notifyDelayRenderRunnableList();
    }

    private void notifyDelayRenderRunnableList(){
        if (mDelayRenderRunnableList != null) {
            ArrayList<Runnable> list = mDelayRenderRunnableList;
            mDelayRenderRunnableList = null;
            if (!list.isEmpty()) {
                for (Runnable runnable : list) {
                    runnable.run();
                }
            }
        }
    }

View on GitHub (pinned to 026e7d4866)

Solutions

  1. Delete the direct call and rely on QMUI's internal onAnimationEnd dispatch.
  2. Override onEnterAnimationEnd() in your subclass to run custom end-of-animation logic, calling super first.
  3. If you must react to animation completion externally, attach your own Animator listener to the animation you started, not to this callback.
  4. Use isInEnterAnimationLiveData observers instead of forcing the callback.

Example fix

// before
animator.addListener(new AnimatorListenerAdapter() {
    @Override public void onAnimationEnd(Animator a) { fragment.onEnterAnimationEnd(a); }
});
// after
@Override
protected void onEnterAnimationEnd(@Nullable Animator animation) {
    super.onEnterAnimationEnd(animation);
    // custom logic here
}
Defensive patterns

Strategy: validation

Validate before calling

if (fragment instanceof QMUIFragment && !fragment.isAdded()) {
    // framework will dispatch onEnterAnimationEnd itself; do not call manually
}

Try / catch

try {
    // framework-managed transition
} catch (IllegalAccessError e) {
    Log.w(TAG, "lifecycle hook invoked directly", e);
}

Prevention

When it happens

Trigger: Calling fragment.onEnterAnimationEnd(animator) manually from app code, calling it from a custom AnimatorListener to 'speed up' the transition, or any second invocation since mCalled is only checked, never reset here.

Common situations: Custom animation completion listeners that duplicate framework behavior, migration from plain Fragment code where onEnterAnimationEnd didn't exist, and test harnesses invoking protected hooks directly.

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/c982787d07760331. Report an issue: GitHub.