Last active
November 25, 2021 01:22
-
-
Save y-polek/42afe8fdb1518db471fb27e646526a06 to your computer and use it in GitHub Desktop.
'map' and 'combineLatest' transformations for LiveData
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
import android.arch.lifecycle.LiveData | |
import android.arch.lifecycle.MediatorLiveData | |
import android.arch.lifecycle.MutableLiveData | |
fun <X, Y> LiveData<X>.map(func: (X?) -> Y?): MutableLiveData<Y?> { | |
return MediatorLiveData<Y>().apply { | |
addSource(this@map) { x -> value = func(x) } | |
} | |
} | |
fun <X, Y> LiveData<X>.mapSkipNulls(func: (X) -> Y): MutableLiveData<Y> { | |
return MediatorLiveData<Y>().apply { | |
addSource(this@mapSkipNulls) { x -> | |
x ?: return@addSource | |
value = func(x) | |
} | |
} | |
} | |
fun <A, B> LiveData<A>.combineLatest(b: LiveData<B>): LiveData<Pair<A, B>> { | |
return MediatorLiveData<Pair<A, B>>().apply { | |
var lastA: A? = null | |
var lastB: B? = null | |
addSource(this@combineLatest) { | |
if (it == null && value != null) value = null | |
lastA = it | |
if (lastA != null && lastB != null) value = lastA!! to lastB!! | |
} | |
addSource(b) { | |
if (it == null && value != null) value = null | |
lastB = it | |
if (lastA != null && lastB != null) value = lastA!! to lastB!! | |
} | |
} | |
} | |
fun <A, B, C> combineLatest(a: LiveData<A>, b: LiveData<B>, c: LiveData<C>): LiveData<Triple<A?, B?, C?>> { | |
fun Triple<A?, B?, C?>?.copyWithFirst(first: A?): Triple<A?, B?, C?> { | |
if (this@copyWithFirst == null) return Triple<A?, B?, C?>(first, null, null) | |
return [email protected](first = first) | |
} | |
fun Triple<A?, B?, C?>?.copyWithSecond(second: B?): Triple<A?, B?, C?> { | |
if (this@copyWithSecond == null) return Triple<A?, B?, C?>(null, second, null) | |
return [email protected](second = second) | |
} | |
fun Triple<A?, B?, C?>?.copyWithThird(third: C?): Triple<A?, B?, C?> { | |
if (this@copyWithThird == null) return Triple<A?, B?, C?>(null, null, third) | |
return [email protected](third = third) | |
} | |
return MediatorLiveData<Triple<A?, B?, C?>>().apply { | |
addSource(a) { value = value.copyWithFirst(it) } | |
addSource(b) { value = value.copyWithSecond(it) } | |
addSource(c) { value = value.copyWithThird(it) } | |
} | |
} |
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
import android.arch.core.executor.testing.InstantTaskExecutorRule | |
import android.arch.lifecycle.MutableLiveData | |
import android.arch.lifecycle.Observer | |
import org.assertj.core.api.Assertions.assertThat | |
import org.junit.Rule | |
import org.junit.Test | |
import org.mockito.ArgumentMatchers.any | |
import org.mockito.Mockito | |
import org.mockito.Mockito.times | |
import org.mockito.Mockito.verify | |
class LiveDataExtTest { | |
@Rule | |
@JvmField | |
val instantTaskExecutorRule = InstantTaskExecutorRule() | |
@Test | |
fun testMap() { | |
val ints = MutableLiveData<Int>() | |
val squareInts = ints.map { n -> | |
n ?: return@map null | |
n * n | |
} | |
val observer = mock<Observer<Int?>>() | |
squareInts.observeForever(observer) | |
ints.value = 2 | |
assertThat(squareInts.value).isEqualTo(4) | |
ints.value = null | |
assertThat(squareInts.value).isEqualTo(null) | |
ints.value = 3 | |
assertThat(squareInts.value).isEqualTo(9) | |
ints.value = 4 | |
assertThat(squareInts.value).isEqualTo(16) | |
verify(observer, times(4)).onChanged(any()) | |
} | |
@Test | |
fun testMapSkipNulls() { | |
val ints = MutableLiveData<Int>() | |
val squareInts = ints.mapSkipNulls { n -> | |
n * n | |
} | |
val observer = mock<Observer<Int?>>() | |
squareInts.observeForever(observer) | |
ints.value = 2 | |
assertThat(squareInts.value).isEqualTo(4) | |
ints.value = null | |
assertThat(squareInts.value).isEqualTo(4) | |
ints.value = 3 | |
assertThat(squareInts.value).isEqualTo(9) | |
ints.value = 4 | |
assertThat(squareInts.value).isEqualTo(16) | |
verify(observer, times(3)).onChanged(any()) | |
} | |
@Test | |
fun testCombineLatest() { | |
val ints = MutableLiveData<Int>() | |
val chars = MutableLiveData<Char>() | |
val pairs = ints.combineLatest(chars) | |
pairs.observeForever {} | |
ints.value = 1 | |
assertThat(pairs.value).isNull() | |
chars.value = 'A' | |
assertThat(pairs.value).isEqualTo(1 to 'A') | |
ints.value = 2 | |
assertThat(pairs.value).isEqualTo(2 to 'A') | |
chars.value = 'B' | |
assertThat(pairs.value).isEqualTo(2 to 'B') | |
} | |
@Test | |
fun testCombineLatestClearOnNull() { | |
val ints = MutableLiveData<Int>() | |
val chars = MutableLiveData<Char>() | |
val pairs = ints.combineLatest(chars) | |
pairs.observeForever {} | |
ints.value = 1 | |
assertThat(pairs.value).isEqualTo(null) | |
chars.value = 'A' | |
assertThat(pairs.value).isEqualTo(1 to 'A') | |
ints.value = null | |
assertThat(pairs.value).isNull() | |
ints.value = 2 | |
assertThat(pairs.value).isEqualTo(2 to 'A') | |
} | |
@Test | |
fun testCombineLatestCallCount() { | |
val ints = MutableLiveData<Int>() | |
val chars = MutableLiveData<Char>() | |
val pairs = ints.combineLatest(chars) | |
val observer = mock<Observer<Pair<Int, Char>>>() | |
pairs.observeForever(observer) | |
ints.value = 1 | |
chars.value = 'A' | |
ints.value = null | |
chars.value = null | |
ints.value = 2 | |
chars.value = 'B' | |
verify(observer, times(1)).onChanged(1 to 'A') | |
verify(observer, times(1)).onChanged(null) | |
verify(observer, times(1)).onChanged(2 to 'B') | |
} | |
@Test | |
fun testCombineLatestTriple() { | |
val hostLiveData = MutableLiveData<String>() | |
val userLiveData = MutableLiveData<String>() | |
val passwordLiveData = MutableLiveData<String>() | |
val credentialsLiveData = combineLatest(hostLiveData, userLiveData, passwordLiveData) | |
val observer = mock<Observer<Triple<String?, String?, String?>>>() | |
credentialsLiveData.observeForever(observer) | |
assertThat(credentialsLiveData.value).isNull() | |
hostLiveData.value = "Raspberry Pi" | |
assertThat(credentialsLiveData.value).isEqualTo(Triple("Raspberry Pi", null, null)) | |
userLiveData.value = "admin" | |
assertThat(credentialsLiveData.value).isEqualTo(Triple("Raspberry Pi", "admin", null)) | |
passwordLiveData.value = "12345" | |
assertThat(credentialsLiveData.value).isEqualTo(Triple("Raspberry Pi", "admin", "12345")) | |
hostLiveData.value = null | |
assertThat(credentialsLiveData.value).isEqualTo(Triple(null, "admin", "12345")) | |
userLiveData.value = null | |
assertThat(credentialsLiveData.value).isEqualTo(Triple(null, null, "12345")) | |
passwordLiveData.value = null | |
assertThat(credentialsLiveData.value).isEqualTo(Triple(null, null, null)) | |
} | |
private inline fun <reified T: Any> mock(): T = Mockito.mock(T::class.java) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI you could delegate map to Transformations.map (https://developer.android.com/reference/android/arch/lifecycle/Transformations)