mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 11:18:35 +00:00
LiveDataUtil combineLatest.
This commit is contained in:
parent
3c5ad519dd
commit
33e3f78be6
@ -11,6 +11,7 @@ public class DefaultValueLiveData<T> extends MutableLiveData<T> {
|
|||||||
private final T defaultValue;
|
private final T defaultValue;
|
||||||
|
|
||||||
public DefaultValueLiveData(@NonNull T defaultValue) {
|
public DefaultValueLiveData(@NonNull T defaultValue) {
|
||||||
|
super(defaultValue);
|
||||||
this.defaultValue = 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…
x
Reference in New Issue
Block a user