+ * The background function order is run serially, albeit possibly across multiple threads. + *
+ * The background function may not run for all {@param source} updates. Later updates taking priority.
+ */
+ public static LiveData mapAsync(@NonNull LiveData source, @NonNull Function backgroundFunction) {
+ return mapAsync(SignalExecutors.BOUNDED, source, backgroundFunction);
+ }
+
+ /**
+ * Runs the {@param backgroundFunction} on the supplied {@param executor}.
+ *
+ * Regardless of the executor supplied, the background function is run serially.
+ *
+ * The background function may not run for all {@param source} updates. Later updates taking priority.
+ */
+ public static LiveData mapAsync(@NonNull Executor executor, @NonNull LiveData source, @NonNull Function backgroundFunction) {
+ MediatorLiveData outputLiveData = new MediatorLiveData<>();
+ Executor liveDataExecutor = new SerialLiveDataExecutor(executor);
+
+ outputLiveData.addSource(source, currentValue -> liveDataExecutor.execute(() -> outputLiveData.postValue(backgroundFunction.apply(currentValue))));
+
+ return outputLiveData;
+ }
+
/**
* Once there is non-null data on both input {@link LiveData}, the {@link Combine} function is run
* and produces a live data of the combined data.
@@ -64,4 +96,42 @@ public final class LiveDataUtil {
}
}
}
+
+ /**
+ * Executor decorator that runs serially but enqueues just the latest task, dropping any pending task.
+ *
+ * Based on SerialExecutor https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
+ * but modified to represent a queue of size one which is replaced by the latest call to {@link #execute(Runnable)}.
+ */
+ private static final class SerialLiveDataExecutor implements Executor {
+ private final Executor executor;
+ private Runnable next;
+ private Runnable active;
+
+ SerialLiveDataExecutor(@NonNull Executor executor) {
+ this.executor = executor;
+ }
+
+ public synchronized void execute(@NonNull Runnable command) {
+ next = () -> {
+ try {
+ command.run();
+ } finally {
+ scheduleNext();
+ }
+ };
+
+ if (active == null) {
+ scheduleNext();
+ }
+ }
+
+ private synchronized void scheduleNext() {
+ active = next;
+ next = null;
+ if (active != null) {
+ executor.execute(active);
+ }
+ }
+ }
}
diff --git a/app/src/main/res/color/ultramarine_text_button.xml b/app/src/main/res/color/ultramarine_text_button.xml
new file mode 100644
index 0000000000..7de1224175
--- /dev/null
+++ b/app/src/main/res/color/ultramarine_text_button.xml
@@ -0,0 +1,5 @@
+
+