{"record":{"id":"c982787d07760331","repo":"Tencent/QMUI_Android","slug":"don-t-call-onenteranimationend-directly","errorCode":null,"errorMessage":"don't call #onEnterAnimationEnd() directly","messagePattern":"don't call #onEnterAnimationEnd\\(\\) directly","errorType":"exception","errorClass":"IllegalAccessError","httpStatus":null,"severity":"error","filePath":"arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java","lineNumber":1408,"sourceCode":"     * may not be call.\n     * @param animation\n     */\n    protected void onEnterAnimationStart(@Nullable Animator animation) {\n        if (mCalled) {\n            throw new IllegalAccessError(\"don't call #onEnterAnimationStart() directly\");\n        }\n        mCalled = true;\n        mEnterAnimationStatus = ANIMATION_ENTER_STATUS_STARTED;\n        isInEnterAnimationLiveData.setValue(true);\n    }\n\n    /**\n     * may not be call.\n     * @param animation\n     */\n    protected void onEnterAnimationEnd(@Nullable Animator animation) {\n        if (mCalled) {\n            throw new IllegalAccessError(\"don't call #onEnterAnimationEnd() directly\");\n        }\n        mCalled = true;\n        mEnterAnimationStatus = ANIMATION_ENTER_STATUS_END;\n        isInEnterAnimationLiveData.setValue(false);\n        notifyDelayRenderRunnableList();\n    }\n\n    private void notifyDelayRenderRunnableList(){\n        if (mDelayRenderRunnableList != null) {\n            ArrayList<Runnable> list = mDelayRenderRunnableList;\n            mDelayRenderRunnableList = null;\n            if (!list.isEmpty()) {\n                for (Runnable runnable : list) {\n                    runnable.run();\n                }\n            }\n        }\n    }","sourceCodeStart":1390,"sourceCodeEnd":1426,"githubUrl":"https://github.com/Tencent/QMUI_Android/blob/026e7d486677d6593a96689cd590ec3561176717/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java#L1390-L1426","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Delete the direct call and rely on QMUI's internal onAnimationEnd dispatch.","Override onEnterAnimationEnd() in your subclass to run custom end-of-animation logic, calling super first.","If you must react to animation completion externally, attach your own Animator listener to the animation you started, not to this callback.","Use isInEnterAnimationLiveData observers instead of forcing the callback."],"exampleFix":"// before\nanimator.addListener(new AnimatorListenerAdapter() {\n    @Override public void onAnimationEnd(Animator a) { fragment.onEnterAnimationEnd(a); }\n});\n// after\n@Override\nprotected void onEnterAnimationEnd(@Nullable Animator animation) {\n    super.onEnterAnimationEnd(animation);\n    // custom logic here\n}","handlingStrategy":"validation","validationCode":"if (fragment instanceof QMUIFragment && !fragment.isAdded()) {\n    // framework will dispatch onEnterAnimationEnd itself; do not call manually\n}","typeGuard":null,"tryCatchPattern":"try {\n    // framework-managed transition\n} catch (IllegalAccessError e) {\n    Log.w(TAG, \"lifecycle hook invoked directly\", e);\n}","preventionTips":["Attach custom AnimatorListeners instead of duplicating onAnimationEnd behavior","Call super.onEnterAnimationEnd() only from within an override","Use LiveData observers rather than forcing the callback"],"tags":["android","lifecycle","reflection-misuse","qmui-fragment"],"backgroundTag":"invalid-state-transition","analyzedSha":"026e7d486677d6593a96689cd590ec3561176717","analyzedAt":"2026-09-06T13:32:24.816Z","contentChangedAt":"2026-09-06T13:32:24.816Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}