{"record":{"id":"4c87e554c2a0d061","repo":"Tencent/tinker","slug":"listener-is-already-set-4c87e5","errorCode":null,"errorMessage":"listener is already set.","messagePattern":"listener is already set\\.","errorType":"exception","errorClass":"TinkerRuntimeException","httpStatus":null,"severity":"error","filePath":"tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java","lineNumber":411,"sourceCode":"        }\n\n        public Builder patchReporter(PatchReporter patchReporter) {\n            if (patchReporter == null) {\n                throw new TinkerRuntimeException(\"patchReporter must not be null.\");\n            }\n            if (this.patchReporter != null) {\n                throw new TinkerRuntimeException(\"patchReporter is already set.\");\n            }\n            this.patchReporter = patchReporter;\n            return this;\n        }\n\n        public Builder listener(PatchListener listener) {\n            if (listener == null) {\n                throw new TinkerRuntimeException(\"listener must not be null.\");\n            }\n            if (this.listener != null) {\n                throw new TinkerRuntimeException(\"listener is already set.\");\n            }\n            this.listener = listener;\n            return this;\n        }\n\n        public Builder customPatcher(AbstractFilePatch patcher) {\n            this.patcher = patcher;\n            return this;\n        }\n\n        public Tinker build() {\n            if (status == -1) {\n                status = ShareConstants.TINKER_ENABLE_ALL;\n            }\n\n            if (loadReporter == null) {\n                loadReporter = new DefaultLoadReporter(context);\n            }","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java#L393-L429","documentation":"Thrown by Tinker.Builder.listener when a PatchListener was already assigned. The builder's single-assignment rule guarantees one filter governs which patches get applied; a second listener call signals duplicated initialization logic.","triggerScenarios":"Two builder.listener(...) calls on the same Builder — e.g. an app registers DefaultPatchListener in onCreate and an analytics module registers its own listener afterwards.","commonSituations":"Multiple teams/SDKs embedding Tinker independently, init code invoked in both attachBaseContext and onCreate, or a retained Builder being reconfigured after a pull-to-refresh/patch-check retry path.","solutions":["Register the PatchListener once, in the single Tinker.install chain in ApplicationLike#onCreate.","Guard initialization with if (!Tinker.isTinkerInstalled()) so re-entry cannot reconfigure the Builder.","Combine multiple filtering concerns into one PatchListener implementation instead of registering several."],"exampleFix":"// before\nTinker tinker = new Tinker.Builder(app).listener(new DefaultPatchListener(app))...\nTinker.install(tinker);\n// later, another module:\nbuilder.listener(new AdsPatchListener(app)); // throws\n\n// after\nif (!Tinker.isTinkerInstalled()) {\n    Tinker.install(new Tinker.Builder(app).listener(new AppPatchListener(app))...build());\n}","handlingStrategy":"validation","validationCode":"if (!Tinker.isTinkerInstalled()) {\n    Tinker.install(new Tinker.Builder(app)\n        .listener(new DefaultPatchListener(app))\n        .build());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register the listener in the single install chain only.","Have SDKs that need patch events observe the one PatchListener rather than registering their own.","Guard install with isTinkerInstalled() to make double configuration impossible."],"tags":["tinker","android","builder","double-init","patch-listener"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}