Build Source API Documentation License

LiveData Extensions

Maven Central Sonatype Nexus (Snapshots)

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

Maven Central Sonatype Nexus (Snapshots)

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

Maven Central Sonatype Nexus (Snapshots)

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

Maven Central Sonatype Nexus (Snapshots)

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) {
        // ...
    }
}