• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.airbnb.lottie;
2 
3 import android.os.Handler;
4 import android.os.Looper;
5 import androidx.annotation.Nullable;
6 import androidx.annotation.RestrictTo;
7 
8 import com.airbnb.lottie.utils.Logger;
9 
10 import java.util.ArrayList;
11 import java.util.LinkedHashSet;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.concurrent.Callable;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.Executor;
17 import java.util.concurrent.Executors;
18 import java.util.concurrent.FutureTask;
19 
20 /**
21  * Helper to run asynchronous tasks with a result.
22  * Results can be obtained with {@link #addListener(LottieListener)}.
23  * Failures can be obtained with {@link #addFailureListener(LottieListener)}.
24  *
25  * A task will produce a single result or a single failure.
26  */
27 public class LottieTask<T> {
28 
29   /**
30    * Set this to change the executor that LottieTasks are run on. This will be the executor that composition parsing and url
31    * fetching happens on.
32    *
33    * You may change this to run deserialization synchronously for testing.
34    */
35   @SuppressWarnings("WeakerAccess")
36   public static Executor EXECUTOR = Executors.newCachedThreadPool();
37 
38   /* Preserve add order. */
39   private final Set<LottieListener<T>> successListeners = new LinkedHashSet<>(1);
40   private final Set<LottieListener<Throwable>> failureListeners = new LinkedHashSet<>(1);
41   private final Handler handler = new Handler(Looper.getMainLooper());
42 
43   @Nullable private volatile LottieResult<T> result = null;
44 
45   @RestrictTo(RestrictTo.Scope.LIBRARY)
LottieTask(Callable<LottieResult<T>> runnable)46   public LottieTask(Callable<LottieResult<T>> runnable) {
47     this(runnable, false);
48   }
49 
50   /**
51    * runNow is only used for testing.
52    */
53   @RestrictTo(RestrictTo.Scope.LIBRARY)
LottieTask(Callable<LottieResult<T>> runnable, boolean runNow)54   LottieTask(Callable<LottieResult<T>> runnable, boolean runNow) {
55     if (runNow) {
56       try {
57         setResult(runnable.call());
58       } catch (Throwable e) {
59         setResult(new LottieResult<T>(e));
60       }
61     } else {
62       EXECUTOR.execute(new LottieFutureTask(runnable));
63     }
64   }
65 
setResult(@ullable LottieResult<T> result)66   private void setResult(@Nullable LottieResult<T> result) {
67     if (this.result != null) {
68       throw new IllegalStateException("A task may only be set once.");
69     }
70     this.result = result;
71     notifyListeners();
72   }
73 
74   /**
75    * Add a task listener. If the task has completed, the listener will be called synchronously.
76    * @return the task for call chaining.
77    */
addListener(LottieListener<T> listener)78   public synchronized LottieTask<T> addListener(LottieListener<T> listener) {
79     if (result != null && result.getValue() != null) {
80       listener.onResult(result.getValue());
81     }
82 
83     successListeners.add(listener);
84     return this;
85   }
86 
87   /**
88    * Remove a given task listener. The task will continue to execute so you can re-add
89    * a listener if neccesary.
90    * @return the task for call chaining.
91    */
removeListener(LottieListener<T> listener)92   public synchronized LottieTask<T> removeListener(LottieListener<T> listener) {
93     successListeners.remove(listener);
94     return this;
95   }
96 
97   /**
98    * Add a task failure listener. This will only be called in the even that an exception
99    * occurs. If an exception has already occurred, the listener will be called immediately.
100    * @return the task for call chaining.
101    */
addFailureListener(LottieListener<Throwable> listener)102   public synchronized LottieTask<T> addFailureListener(LottieListener<Throwable> listener) {
103     if (result != null && result.getException() != null) {
104       listener.onResult(result.getException());
105     }
106 
107     failureListeners.add(listener);
108     return this;
109   }
110 
111   /**
112    * Remove a given task failure listener. The task will continue to execute so you can re-add
113    * a listener if neccesary.
114    * @return the task for call chaining.
115    */
removeFailureListener(LottieListener<Throwable> listener)116   public synchronized LottieTask<T> removeFailureListener(LottieListener<Throwable> listener) {
117     failureListeners.remove(listener);
118     return this;
119   }
120 
notifyListeners()121   private void notifyListeners() {
122     // Listeners should be called on the main thread.
123     handler.post(new Runnable() {
124       @Override public void run() {
125         if (result == null) {
126           return;
127         }
128         // Local reference in case it gets set on a background thread.
129         LottieResult<T> result = LottieTask.this.result;
130         if (result.getValue() != null) {
131           notifySuccessListeners(result.getValue());
132         } else {
133           notifyFailureListeners(result.getException());
134         }
135       }
136     });
137   }
138 
notifySuccessListeners(T value)139   private synchronized void notifySuccessListeners(T value) {
140     // Allows listeners to remove themselves in onResult.
141     // Otherwise we risk ConcurrentModificationException.
142     List<LottieListener<T>> listenersCopy = new ArrayList<>(successListeners);
143     for (LottieListener<T> l : listenersCopy) {
144       l.onResult(value);
145     }
146   }
147 
notifyFailureListeners(Throwable e)148   private synchronized void notifyFailureListeners(Throwable e) {
149     // Allows listeners to remove themselves in onResult.
150     // Otherwise we risk ConcurrentModificationException.
151     List<LottieListener<Throwable>> listenersCopy = new ArrayList<>(failureListeners);
152     if (listenersCopy.isEmpty()) {
153       Logger.warning("Lottie encountered an error but no failure listener was added:", e);
154       return;
155     }
156 
157     for (LottieListener<Throwable> l : listenersCopy) {
158       l.onResult(e);
159     }
160   }
161 
162   private class LottieFutureTask extends FutureTask<LottieResult<T>> {
LottieFutureTask(Callable<LottieResult<T>> callable)163     LottieFutureTask(Callable<LottieResult<T>> callable) {
164       super(callable);
165     }
166 
167     @Override
done()168     protected void done() {
169       if (isCancelled()) {
170         // We don't need to notify and listeners if the task is cancelled.
171         return;
172       }
173 
174       try {
175         setResult(get());
176       } catch (InterruptedException | ExecutionException e) {
177         setResult(new LottieResult<T>(e));
178       }
179     }
180   }
181 }
182