Tencent/tinker · error · TinkerRuntimeException
listener is already set.
Error message
listener is already set.
What it means
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.
Source
Thrown at tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java:411
}
public Builder patchReporter(PatchReporter patchReporter) {
if (patchReporter == null) {
throw new TinkerRuntimeException("patchReporter must not be null.");
}
if (this.patchReporter != null) {
throw new TinkerRuntimeException("patchReporter is already set.");
}
this.patchReporter = patchReporter;
return this;
}
public Builder listener(PatchListener listener) {
if (listener == null) {
throw new TinkerRuntimeException("listener must not be null.");
}
if (this.listener != null) {
throw new TinkerRuntimeException("listener is already set.");
}
this.listener = listener;
return this;
}
public Builder customPatcher(AbstractFilePatch patcher) {
this.patcher = patcher;
return this;
}
public Tinker build() {
if (status == -1) {
status = ShareConstants.TINKER_ENABLE_ALL;
}
if (loadReporter == null) {
loadReporter = new DefaultLoadReporter(context);
}View on GitHub (pinned to 1b7ea02c23)
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.
Example fix
// before
Tinker tinker = new Tinker.Builder(app).listener(new DefaultPatchListener(app))...
Tinker.install(tinker);
// later, another module:
builder.listener(new AdsPatchListener(app)); // throws
// after
if (!Tinker.isTinkerInstalled()) {
Tinker.install(new Tinker.Builder(app).listener(new AppPatchListener(app))...build());
} Defensive patterns
Strategy: validation
Validate before calling
if (!Tinker.isTinkerInstalled()) {
Tinker.install(new Tinker.Builder(app)
.listener(new DefaultPatchListener(app))
.build());
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- tinkerFlag is already set.
- tinkerLoadVerifyFlag is already set.
- loadReporter is already set.
- patchReporter is already set.
- listener must not be null.
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/4c87e554c2a0d061.
Report an issue: GitHub.