didi/DoKit · error · IllegalStateException

Singleton instance already exists.

Error message

Singleton instance already exists.

What it means

setSingletonInstance(DokitPicasso) installs the object returned by with(); it may be called at most once and only before the first with() call. A second call — or any call after with() already lazily created the default — throws IllegalStateException('Singleton instance already exists.'). This protects callers that captured with()'s instance from having it swapped underneath them.

Solutions

  1. Call setSingletonInstance exactly once, in Application.onCreate(), before any image load
  2. Wrap test setup: only call it if the singleton is not yet set (guarded by a try/catch or your own initialized flag)
  3. Remove stray with() calls (e.g. in static initializers) that run before your setSingletonInstance
  4. In multi-test processes, prefer building and injecting Picasso instances directly instead of touching the singleton

Example fix

// before
public void onCreate() {
  super.onCreate();
  DokitPicasso.setSingletonInstance(defaultPicasso); // crashes on 2nd init
}

// after
private static boolean picassoInstalled;
public void onCreate() {
  super.onCreate();
  if (!picassoInstalled) {
    try {
      DokitPicasso.setSingletonInstance(defaultPicasso);
    } catch (IllegalStateException ignored) { /* already installed */ }
    picassoInstalled = true;
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Install once, before any with()/load call — typically in Application.onCreate:
public class App extends Application {
  @Override public void onCreate() {
    super.onCreate();
    synchronized (DokitPicasso.class) {
      try {
        DokitPicasso.setSingletonInstance(
            new DokitPicasso.Builder(this).build());
      } catch (IllegalStateException alreadyInstalled) {
        // second init in the same process (tests, relaunch) — safe to ignore
      }
    }
  }
}

Try / catch

try {
  DokitPicasso.setSingletonInstance(picasso);
} catch (IllegalStateException e) {
  if ("Singleton instance already exists.".equals(e.getMessage())) {
    Log.d(TAG, "Singleton already installed; reusing it");
  } else throw e;
}

Prevention

When it happens

Trigger: Calling setSingletonInstance(picasso) in both Application.onCreate and a test setup; two libraries each installing their own singleton; calling with(context) anywhere first (initializes the default) and then setSingletonInstance.

Common situations: Robolectric/JUnit test suites reusing one process where app onCreate runs per test; crash-reporting or leak-canning code re-initializing on relaunch within the same process; migration code adding setSingletonInstance to an app that already called with() eagerly.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/5acf6c0a4c49896d. Report an issue: GitHub.

Appendix: source

Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/DokitPicasso.java:678

    if (singleton == null) {
      synchronized (DokitPicasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

  /**
   * Set the global instance returned from {@link #with}.
   * <p>
   * This method must be called before any calls to {@link #with} and may only be called once.
   */
  public static void setSingletonInstance(DokitPicasso picasso) {
    synchronized (DokitPicasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }

  /** Fluent API for creating {@link DokitPicasso} instances. */
  @SuppressWarnings("UnusedDeclaration") // Public API.
  public static class Builder {
    private final Context context;
    private Downloader downloader;
    private ExecutorService service;
    private Cache cache;
    private Listener listener;
    private RequestTransformer transformer;
    private List<RequestHandler> requestHandlers;
    private Bitmap.Config defaultBitmapConfig;

    private boolean indicatorsEnabled;

View on GitHub (pinned to 626827cddb)