LiveData Extensions
Build Configuration
Kotlin
dependencies {
implementation("it.czerwinski.android.lifecycle:lifecycle-livedata:[VERSION]")
}
Groovy
dependencies {
implementation 'it.czerwinski.android.lifecycle:lifecycle-livedata:[VERSION]'
}
Types
ConstantLiveData
LiveData that always emits a single constant value.
GroupedLiveData
MediatorLiveData subclass which provides a separate LiveData per each result returned by keySelector
function
executed on subsequent values emitted by the source LiveData.
LiveData Factory Methods
intervalLiveData
Returns a LiveData emitting a sequence of integer values, spaced by a given timeInMillis
.
val fixedIntervalLiveData: LiveData<Int> = intervalLiveData(timeInMillis = 1000L)
val varyingIntervalLiveData: LiveData<Int> = intervalLiveData { index -> (index + 1) * 1000L }
LiveData Transformations
mapNotNull
Returns a LiveData emitting only the non-null results of applying the given transform
function to each value
emitted by this LiveData.
val userOptionLiveData: LiveData<Option<User>> = // ...
val userLiveData: LiveData<User> = userOptionLiveData.mapNotNull { user -> user.getOrNull() }
filter
Returns a LiveData emitting only values from this LiveData matching the given predicate
.
val resultLiveData: LiveData<Try<User>> = // ...
val successLiveData: LiveData<Try<User>> = resultLiveData.filter { it.isSuccess }
filterNotNull
Returns a LiveData emitting only non-null values from this LiveData.
val userLiveData: LiveData<User?> = // ...
val nonNullUserLiveData: LiveData<User> = userLiveData.filterNotNull()
filterIsInstance
Returns a LiveData emitting only values of the given type from this LiveData.
val resultLiveData: LiveData<Try<User>> = // ...
val failureLiveData: LiveData<Failure> = resultLiveData.filterIsInstance<Failure>()
reduce
Returns a LiveData emitting accumulated value starting with the first value emitted by this LiveData and applying
operation
from left to right to current accumulator value and each value emitted by this.
val newOperationsCountLiveData: LiveData<Int?> = // ...
val operationsCountLiveData: LiveData<Int?> =
newOperationsCountLiveData.reduce { acc, next -> if (next == null) null else acc + next }
reduceNotNull
Returns a LiveData emitting non-null accumulated value starting with the first non-null value emitted by this
LiveData and applying operation
from left to right to current accumulator value and each subsequent non-null value
emitted by this LiveData.
val newOperationsCountLiveData: LiveData<Int> = // ...
val operationsCountLiveData: LiveData<Int> =
newOperationsCountLiveData.reduceNotNull { acc, next -> acc + next }
throttleWithTimeout
Returns a LiveData emitting values from this LiveData, after dropping values followed by newer values before
timeInMillis
expires.
val isLoadingLiveData: LiveData<Boolean> = // ...
val isLoadingThrottledLiveData: LiveData<Boolean> = isLoadingLiveData.throttleWithTimeout(
timeInMillis = 1000L,
context = viewModelScope.coroutineContext
)
delayStart
Returns a LiveData emitting values from this LiveData, after dropping values followed by newer values before
timeInMillis
expires since the result LiveData has been created.
val resultLiveData: LiveData<ResultData> = // ...
val delayedResultLiveData: LiveData<ResultData> = resultLiveData.delayStart(
timeInMillis = 1000L,
context = viewModelScope.coroutineContext
)
merge
Returns a LiveData emitting each value emitted by any of the given LiveData.
val serverError: LiveData<String> = // ...
val databaseError: LiveData<String> = // ...
val error: LiveData<String> = serverError merge databaseError
val serverError: LiveData<String> = // ...
val databaseError: LiveData<String> = // ...
val fileError: LiveData<String> = // ...
val error: LiveData<String> = merge(serverError, databaseError, fileError)
combineLatest
Returns a LiveData emitting pairs, triples or lists of latest values emitted by the given LiveData.
val userLiveData: LiveData<User> = // ...
val avatarUrlLiveData: LiveData<String> = // ...
val userWithAvatar: LiveData<Pair<User?, String?>> = combineLatest(userLiveData, avatarUrlLiveData)
val userLiveData: LiveData<User> = // ...
val avatarUrlLiveData: LiveData<String> = // ...
val userWithAvatar: LiveData<UserWithAvatar> =
combineLatest(userLiveData, avatarUrlLiveData) { user, avatarUrl ->
UserWithAvatar(user, avatarUrl)
}
switch
Converts LiveData that emits other LiveData into a single LiveData that emits the items emitted by the most recently emitted LiveData.
val sourcesLiveData: LiveData<LiveData<String>> = // ...
val resultLiveData: LiveData<String?> = sourcesLiveData.switch()
groupBy
Returns a GroupedLiveData
providing a set of LiveData, each emitting a different subset of values from this
LiveData, based on the result of the given keySelector
function.
val userLiveData: LiveData<User> = // ...
val userByStatusLiveData: GroupedLiveData<UserStatus, User> = errorLiveData.groupBy { user -> user.status }
val activeUserLiveData: LiveData<User> = userByStatusLiveData[UserStatus.ACTIVE]
defaultIfEmpty
Returns a LiveData that emits the values emitted by this LiveData or a specified default value if this LiveData has not yet emitted any values at the time of observing.
val errorLiveData: LiveData<String> = // ...
val statusLiveData: LiveData<String?> = errorLiveData.defaultIfEmpty("No errors")
MediatorLiveData Extensions
addDirectSource
Starts to listen the given source LiveData. Whenever source value is changed, it is set as a new value of this MediatorLiveData.
mediatorLiveData.addDirectSource(liveData)
is equivalent to:
mediatorLiveData.addSource(liveData) { x -> mediatorLiveData.value = x }
Common LivaData Testing Utilities
Available since v1.1.0.
Build Configuration
This package is included in both lifecycle-livedata-test-junit4
and lifecycle-livedata-test-junit5
.
Kotlin
dependencies {
testImplementation("junit:junit:4.13.1")
testImplementation("it.czerwinski.android.lifecycle:lifecycle-livedata-test-common:[VERSION]")
}
Groovy
dependencies {
testImplementation 'junit:junit:4.13.1'
testImplementation 'it.czerwinski.android.lifecycle:lifecycle-livedata-test-common:[VERSION]'
}
Testing Observed Values
TestObserver
A callback testing values emitted by LiveData.
class MyTestClass {
@Test
fun testMethod1() {
MutableLiveData<Int>()
.test()
.assertNoValues()
}
@Test
fun testMethod2() {
val liveData = MutableLiveData<Int>()
val observer = liveData.test()
liveData.postValue(1)
liveData.postValue(2)
liveData.postValue(3)
observer.assertValues(1, 2, 3)
}
}
LivaData Testing Utilities For JUnit4
Build Configuration
Kotlin
dependencies {
testImplementation("junit:junit:4.13.1")
testImplementation("it.czerwinski.android.lifecycle:lifecycle-livedata-test-junit4:[VERSION]")
}
Groovy
dependencies {
testImplementation 'junit:junit:4.13.1'
testImplementation 'it.czerwinski.android.lifecycle:lifecycle-livedata-test-junit4:[VERSION]'
}
JUnit4 Rules
TestCoroutineDispatcherRule
JUnit4 test rule that swaps main coroutine dispatcher with UnconfinedTestDispatcher. Note that TestCoroutineDispatcher is now deprecated.
class MyTestClass {
@Rule
@JvmField
val testCoroutineDispatcherRule = TestCoroutineDispatcherRule()
val testCoroutineScheduler get() = testCoroutineDispatcherRule.scheduler
// ...
}
LivaData Testing Utilities For JUnit5
Build Configuration
Kotlin
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
testImplementation("it.czerwinski.android.lifecycle:lifecycle-livedata-test-junit5:[VERSION]")
}
Groovy
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
testImplementation 'it.czerwinski.android.lifecycle:lifecycle-livedata-test-junit5:[VERSION]'
}
JUnit5 Extensions
InstantTaskExecutorExtension
JUnit5 extension that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.
This extension is analogous to InstantTaskExecutorRule for JUnit4.
@ExtendWith(InstantTaskExecutorExtension::class)
class MyTestClass {
// ...
}
TestCoroutineDispatcherExtension
JUnit5 extension that swaps main coroutine dispatcher with UnconfinedTestDispatcher. Note that TestCoroutineDispatcher is now deprecated.
Any test method parameter of type TestCoroutineScheduler will be resolved as the scheduler of the TestCoroutineDispatcher:
@ExtendWith(TestCoroutineDispatcherExtension::class)
class MyTestClass {
@Test
fun someTest(scheduler: TestCoroutineScheduler) {
// ...
scheduler.advanceTimeBy(delayTimeMillis = 1000L)
scheduler.runCurrent()
// ...
}
}
In case of parameterized tests, the scheduler can be passed as a parameter of a before method:
@ExtendWith(TestCoroutineDispatcherExtension::class)
class MyTestClass {
private lateinit var testCoroutineScheduler: TestCoroutineScheduler
@BeforeEach
fun setScheduler(scheduler: TestCoroutineScheduler) {
testCoroutineScheduler = scheduler
}
@ParameterizedTest
@MethodSource("testData")
fun someParameterizedTest(input: Int) {
// ...
}
}