-
-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3c5ad51
commit 33e3f78
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
app/src/main/java/org/thoughtcrime/securesms/util/livedata/LiveDataUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
app/src/test/java/org/thoughtcrime/securesms/util/livedata/LiveDataRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
app/src/test/java/org/thoughtcrime/securesms/util/livedata/LiveDataTestUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
app/src/test/java/org/thoughtcrime/securesms/util/livedata/LiveDataUtilTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} |