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

  1. Centralize Tinker initialization in one place (usually ApplicationLike#onCreate) and set the patch reporter only there.
  2. If an SDK also initializes Tinker, coordinate on who owns the install or check Tinker.isTinkerInstalled() before configuring.
  3. 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

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


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/e236b711558914f5. Report an issue: GitHub.