airbnb/lottie-android · error · IllegalStateException

A task may only be set once.

Error message

A task may only be set once.

What it means

Thrown by LottieTask#setResult when result has already been assigned once. setResult is an internal single-assignment guard invoked from the task's runnable (inline on the main thread or via EXECUTOR); a second call indicates the task's result was set twice, which violates LottieTask's one-shot contract.

Source

Thrown at lottie/src/main/java/com/airbnb/lottie/LottieTask.java:95

  /**
   * runNow is only used for testing.
   */
  @RestrictTo(RestrictTo.Scope.LIBRARY) LottieTask(Looper uiLooper, Callable<LottieResult<T>> runnable, boolean runNow) {
    uiHandler = new Handler(uiLooper);
    if (runNow) {
      try {
        setResult(runnable.call());
      } catch (Throwable e) {
        setResult(new LottieResult<>(e));
      }
    } else {
      EXECUTOR.execute(new LottieFutureTask<T>(this, runnable));
    }
  }

  private void setResult(@Nullable LottieResult<T> result) {
    if (this.result != null) {
      throw new IllegalStateException("A task may only be set once.");
    }
    this.result = result;
    notifyListeners();
  }

  /**
   * Add a task listener. If the task has completed, the listener will be called synchronously.
   *
   * @return the task for call chaining.
   */
  public synchronized LottieTask<T> addListener(LottieListener<T> listener) {
    LottieResult<T> result = this.result;
    if (result != null && result.getValue() != null) {
      listener.onResult(result.getValue());
    }

    successListeners.add(listener);
    return this;

View on GitHub (pinned to 05ea92e903)

Solutions

  1. Do not reuse a LottieTask instance - create a new one per load via LottieCompositionFactory.
  2. Upgrade lottie-android; this guard is internal and double-set indicates a bug or unsupported reuse.
  3. If triggering from custom code, ensure you only consume the task's result via addListener/addFailureListener and never drive setResult yourself.

Example fix

// before - reusing/retrying the same task
task.removeListener(...); runSameTaskAgain(task);

// after - start a fresh task per attempt
LottieTask<LottieComposition> task = LottieCompositionFactory.fromAsset(context, fileName);
Defensive patterns

Strategy: try-catch

Try / catch

// Treat task as one-shot; never reuse. If you must retry, start a new task:
try {
  task.addListener(c -> render(c)).addFailureListener(t -> handle(t));
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().contains("set once")) {
    task = LottieCompositionFactory.fromAsset(ctx, fileName); // fresh task
  } else throw e;
}

Prevention

When it happens

Trigger: Internal: the same LottieTask instance having its runnable complete twice (e.g. a reused task, a double notify, or a race where EXECUTOR and an inline path both call setResult). Not normally reachable through public API unless a task reference is reused or the listener pipeline re-enters setResult.

Common situations: Library version with a concurrency regression; manually poking a LottieTask; a wrapped/retry layer that re-runs a task that already produced a result.

Related errors


AI-assisted analysis of airbnb/lottie-android@05ea92e903 (2026-08-14). Data as JSON: /api/errors/8384ec3d36f6b4a3. Report an issue: GitHub.