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
- Do not reuse a LottieTask instance - create a new one per load via LottieCompositionFactory.
- Upgrade lottie-android; this guard is internal and double-set indicates a bug or unsupported reuse.
- 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
- Never reuse a LottieTask; request a new one from LottieCompositionFactory for each load.
- Keep a single ownership point for each load to avoid concurrent result setting.
- Upgrade lottie-android if you see this spuriously - it is normally internal.
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.