Tencent/tinker · error · TinkerRuntimeException
patchReporter is already set.
Error message
patchReporter is already set.
What it means
Thrown by Tinker.Builder.patchReporter when a PatchReporter was already set on the same Builder. Like the other collaborators, Tinker allows exactly one patch reporter so patch-apply events have an unambiguous target. A second call means initialization code ran twice.
Source
Thrown at tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java:400
}
public Builder loadReport(LoadReporter loadReporter) {
if (loadReporter == null) {
throw new TinkerRuntimeException("loadReporter must not be null.");
}
if (this.loadReporter != null) {
throw new TinkerRuntimeException("loadReporter is already set.");
}
this.loadReporter = loadReporter;
return this;
}
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;View on GitHub (pinned to 1b7ea02c23)
Solutions
- Centralize Tinker initialization in one place (usually ApplicationLike#onCreate) and set the patch reporter only there.
- If an SDK also initializes Tinker, coordinate on who owns the install or check Tinker.isTinkerInstalled() before configuring.
- Do not cache the Builder in a static field that survives re-entry; create it fresh inside the install path.
Example fix
// before builder.patchReport(new DefaultPatchReporter(context)); builder.patchReport(new SdkPatchReporter(context)); // throws // after builder.patchReport(new CompositePatchReporter(context, Arrays.asList(new DefaultPatchReporter(context), new SdkPatchReporter(context))));
Defensive patterns
Strategy: validation
Validate before calling
if (!Tinker.isTinkerInstalled()) {
Tinker.install(new Tinker.Builder(app)
.patchReport(new DefaultPatchReporter(app))
.build());
} // second call never happens because install is idempotent-guarded Prevention
- Own Tinker initialization in exactly one class; other modules read Tinker.with(app) instead of reconfiguring.
- Wrap install in an isTinkerInstalled() guard to survive multi-entry startup.
- If two reporters are needed, compose them into one PatchReporter implementation.
When it happens
Trigger: Two sequential builder.patchReporter(...) calls on one Builder — commonly when the Builder is a static/singleton and both the app and an SDK module configure it, or when install logic sits in a method invoked from multiple lifecycle paths.
Common situations: SDK integrations where a third-party library also initializes Tinker, multi-process code that accidentally shares the Builder, or a hot-restart of an Activity triggering Application.onCreate-style init a second time on a retained Builder.
Related errors
- tinkerFlag is already set.
- tinkerLoadVerifyFlag is already set.
- loadReporter is already set.
- patchReporter must not be null.
- listener is already set.
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/e236b711558914f5.
Report an issue: GitHub.