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

  1. Register the PatchListener once, in the single Tinker.install chain in ApplicationLike#onCreate.
  2. Guard initialization with if (!Tinker.isTinkerInstalled()) so re-entry cannot reconfigure the Builder.
  3. 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

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


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