LiveDataUtil combineLatest.
This commit is contained in:
parent
3c5ad519dd
commit
33e3f78be6
5 changed files with 280 additions and 0 deletions
|
@ -11,6 +11,7 @@ public class DefaultValueLiveData<T> extends MutableLiveData<T> {
|
|||
private final T defaultValue;
|
||||
|
||||
public DefaultValueLiveData(@NonNull T defaultValue) {
|
||||
super(defaultValue);
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
|
||||
public final class LiveDataUtil {
|
||||
|
||||
private LiveDataUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* As each live data changes, the combine function is re-run, and a new value is emitted always
|
||||
* with the latest, non-null values.
|
||||
*/
|
||||
public static <A, B, R> LiveData<R> combineLatest(@NonNull LiveData<A> a,
|
||||
@NonNull LiveData<B> b,
|
||||
@NonNull Combine<A, B, R> combine) {
|
||||
return new CombineLiveData<>(a, b, combine);
|
||||
}
|
||||
|
||||
public interface Combine<A, B, R> {
|
||||
@NonNull R apply(@NonNull A a, @NonNull B b);
|
||||
}
|
||||
|
||||
private static final class CombineLiveData<A, B, R> extends MediatorLiveData<R> {
|
||||
private A a;
|
||||
private B b;
|
||||
|
||||
CombineLiveData(LiveData<A> liveDataA, LiveData<B> liveDataB, Combine<A, B, R> combine) {
|
||||
if (liveDataA == liveDataB) {
|
||||
|
||||
addSource(liveDataA, (a) -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
//noinspection unchecked: A is B if live datas are same instance
|
||||
this.b = (B) a;
|
||||
setValue(combine.apply(a, b));
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
addSource(liveDataA, (a) -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
if (b != null) {
|
||||
setValue(combine.apply(a, b));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addSource(liveDataB, (b) -> {
|
||||
if (b != null) {
|
||||
this.b = b;
|
||||
if (a != null) {
|
||||
setValue(combine.apply(a, b));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||
import androidx.arch.core.executor.TaskExecutor;
|
||||
|
||||
import org.junit.rules.TestWatcher;
|
||||
import org.junit.runner.Description;
|
||||
|
||||
/**
|
||||
* Copy of androidx.arch.core.executor.testing.InstantTaskExecutorRule.
|
||||
* <p>
|
||||
* I didn't want to bring in androidx.arch.core:core-testing at this time.
|
||||
*/
|
||||
public final class LiveDataRule extends TestWatcher {
|
||||
@Override
|
||||
protected void starting(Description description) {
|
||||
super.starting(description);
|
||||
|
||||
ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
|
||||
@Override
|
||||
public void executeOnDiskIO(@NonNull Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postToMainThread(@NonNull Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMainThread() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finished(Description description) {
|
||||
super.finished(description);
|
||||
ArchTaskExecutor.getInstance().setDelegate(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public final class LiveDataTestUtil {
|
||||
|
||||
/**
|
||||
* Observes and then instantly un-observes the supplied live data.
|
||||
* <p>
|
||||
* This will therefore only work in conjunction with {@link LiveDataRule}.
|
||||
*/
|
||||
public static <T> T getValue(final LiveData<T> liveData) {
|
||||
AtomicReference<T> data = new AtomicReference<>();
|
||||
Observer<T> observer = data::set;
|
||||
|
||||
liveData.observeForever(observer);
|
||||
liveData.removeObserver(observer);
|
||||
|
||||
return data.get();
|
||||
}
|
||||
|
||||
public static <T> void assertNoValue(final LiveData<T> liveData) {
|
||||
AtomicReference<Boolean> data = new AtomicReference<>(false);
|
||||
Observer<T> observer = newValue -> data.set(true);
|
||||
|
||||
liveData.observeForever(observer);
|
||||
liveData.removeObserver(observer);
|
||||
|
||||
assertFalse("Expected no value", data.get());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.thoughtcrime.securesms.util.livedata.LiveDataTestUtil.assertNoValue;
|
||||
import static org.thoughtcrime.securesms.util.livedata.LiveDataTestUtil.getValue;
|
||||
|
||||
public final class LiveDataUtilTest {
|
||||
|
||||
@Rule
|
||||
public TestRule rule = new LiveDataRule();
|
||||
|
||||
@Test
|
||||
public void initially_no_value() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
assertNoValue(combined);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void no_value_after_just_a() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataA.setValue("Hello, ");
|
||||
|
||||
assertNoValue(combined);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void no_value_after_just_b() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataB.setValue("World!");
|
||||
|
||||
assertNoValue(combined);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void combined_value_after_a_and_b() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataA.setValue("Hello, ");
|
||||
liveDataB.setValue("World!");
|
||||
|
||||
assertEquals("Hello, World!", getValue(combined));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void on_update_a() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataA.setValue("Hello, ");
|
||||
liveDataB.setValue("World!");
|
||||
|
||||
assertEquals("Hello, World!", getValue(combined));
|
||||
|
||||
liveDataA.setValue("Welcome, ");
|
||||
assertEquals("Welcome, World!", getValue(combined));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void on_update_b() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataA.setValue("Hello, ");
|
||||
liveDataB.setValue("World!");
|
||||
|
||||
assertEquals("Hello, World!", getValue(combined));
|
||||
|
||||
liveDataB.setValue("Joe!");
|
||||
assertEquals("Hello, Joe!", getValue(combined));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void combined_same_instance() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataA, (a, b) -> a + b);
|
||||
|
||||
liveDataA.setValue("Echo! ");
|
||||
|
||||
assertEquals("Echo! Echo! ", getValue(combined));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void on_a_set_before_combine() {
|
||||
MutableLiveData<String> liveDataA = new MutableLiveData<>();
|
||||
MutableLiveData<String> liveDataB = new MutableLiveData<>();
|
||||
|
||||
liveDataA.setValue("Hello, ");
|
||||
|
||||
LiveData<String> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a + b);
|
||||
|
||||
liveDataB.setValue("World!");
|
||||
|
||||
assertEquals("Hello, World!", getValue(combined));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void on_default_values() {
|
||||
MutableLiveData<Integer> liveDataA = new DefaultValueLiveData<>(10);
|
||||
MutableLiveData<Integer> liveDataB = new DefaultValueLiveData<>(30);
|
||||
|
||||
LiveData<Integer> combined = LiveDataUtil.combineLatest(liveDataA, liveDataB, (a, b) -> a * b);
|
||||
|
||||
assertEquals(Integer.valueOf(300), getValue(combined));
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue