Lives brings RxJava operators to LiveData, and the docs already warn you about the catch
Lives - Reactive LiveData Extensions for Kotlin and Java
At a glance
- What is it?
- Lives is a library that adds RxJava-style operators to Android LiveData, usable from Kotlin as extension functions and from Java as static methods on a Lives class, covering filtering, combining, transforming and null handling. It suits a codebase that adopted LiveData but kept reaching for RxJava, and its own documentation explains the one LiveData behaviour that breaks the mental model.
- Who is it for?
- Lives fits a team that standardised on LiveData but kept needing RxJava operators, since the library covers the common cases in one import and works from Java as well as Kotlin. It does not fit a new project, because the listed releases stop at 2.0.0 in April 2020 and the last push was on 2026-06-24 with no recent release.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 121 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What it adds to LiveData
Lives is a set of reactive LiveData extensions for Kotlin and Java, and its purpose is stated in one line: to add RxJava-like operators to your LiveData objects with ease, usable in Kotlin as extension functions and in Java as static functions to a class called Lives.
That is a narrow job with a clear audience. LiveData is Android's built-in observable type, and it is deliberately minimal: it holds a value, notifies observers, and does not offer the operator vocabulary that RxJava has. Teams that adopt LiveData often find themselves writing the same combination and filtering logic by hand, or keeping RxJava around for the operations LiveData lacks.
The operator list is broad. Under filtering there is distinct, distinctUntilChanged, filter, first(), take(n), takeUntil(predicate), skip(n), skipUntil(predicate), elementAt(index), nonNull() and defaultIfNull(value).
Under combining there is merge for a list, an instance-level LiveData.merge, concat, an instance-level LiveData.then, startWith, zip, combineLatest and sampleWith.
Under transforming there is map, switchMap, doBeforeNext, doAfterNext, buffer(count), scan with one and two arguments, amb, and toMutableLiveData().
The count is what makes this more than a convenience shim. These are the operators that in RxJava come from separate libraries, and having them as extension functions on LiveData means they compose with the existing Android architecture components without a second reactive type in the app.
The last push to the repository was on 2026-06-24, the default branch is master, and the licence is recorded as one GitHub cannot classify, so the LICENSE file is the text to read.
Installing it, and the confusing dependency headings
The dependency coordinates are com.snakydesign.livedataextensions:lives, and the readme gives two snippets under the headings AndroidX Version and Non AndroidX Version.
implementation 'com.snakydesign.livedataextensions:lives:1.2.1'
implementation 'androidx.lifecycle:lifecycle-livedata:x.x.x' That snippet, which is under the Non AndroidX Version heading, pairs lives 1.2.1 with the androidx.lifecycle artifact. The snippet above it, under the AndroidX Version heading, reads:
implementation 'com.snakydesign.livedataextensions:lives:1.3.0'
implementation 'android.arch.lifecycle:extensions:x.x.x' // If you are using the AndroidX version, that's fine too, as the Jetifier will take care of the conversion.So the headings and the artifact coordinates appear to be the wrong way round in the readme, with 1.2.1 going with AndroidX and 1.3.0 with the old support library plus a Jetifier note. The comment on the second line, that the Jetifier will take care of the conversion if you are on AndroidX, belongs with the 1.3.0 line.
The version history supports the swap. The release 1.3.0 is labelled Migration to AndroidX and came in April 2019, and 1.2.1 came in January 2019 before the migration. So 1.3.0 is the AndroidX-aware build and 1.2.1 is the older one, which is the opposite of what the headings say.
If you are on Java rather than Kotlin, the stdlib has to be added explicitly.
implementation 'org.jetbrains.kotlin:kotlin-stdlib:x.x.x'That is because the library is written in Kotlin and the Java-facing API is static methods on a Kotlin object, so the runtime is a dependency whether or not you write any Kotlin.
Creating LiveData, from Kotlin and from Java
The Kotlin side is a wildcard import of the extension functions.
import com.snakydesign.livedataextensions.*And creating a LiveData has three forms. liveDataOf creates one from a value, like just in RxJava, although it immediately emits the value.
val liveData = liveDataOf(2) //liveData will produce 2 (as Int) when observedThe `from` form creates a LiveData that emits the value the callable produces and emits it immediately.
val liveData = liveDataOf {computePI()}And emptyLiveData creates an empty one.
val liveData = emptyLiveData<Int>()The Java side is the same operations as static methods on Lives, with the Kotlin lambdas expressed as Function1 implementations. The readme's example maps an integer to its string form.
LiveData<String> liveData = Lives.map(Lives.just(2), new Function1<Integer, String>() {
@Override
public Integer invoke(Integer integer) {
return String.valueOf(integer);
}
}) ;Two things to notice. The declared return type of invoke is Integer while the body returns a String, which is a copy-and-paste slip in the documentation rather than in the library. And the Java API is verbose in the way any Kotlin lambda interop is, which is the main practical argument for using the Kotlin extension functions even in a mostly Java codebase.
Filtering, and the difference between distinct and distinctUntilChanged
The filtering operators behave the way their RxJava namesakes do, and the readme shows the difference between the two distinct variants with worked examples.
distinct emits the items that are different from all the values emitted so far.
val originalLiveData = MutableLiveData<Int>()
val newLiveData = originalLiveData.distinct()
originalLiveData.value = 2
originalLiveData.value = 2 // newLiveData will not produce this
originalLiveData.value = 3 // newLiveData will produce
originalLiveData.value = 2 // newLiveData will not produce thisdistinctUntilChanged emits the items that are different from the last item only.
val originalLiveData = MutableLiveData<Int>()
val newLiveData = originalLiveData.distinctUntilChanged()
originalLiveData.value = 2
originalLiveData.value = 2 // newLiveData will not produce this
originalLiveData.value = 3 // newLiveData will produce
originalLiveData.value = 2 // newLiveData will produceThe last line is the difference. After emitting 3, a distinctUntilChanged stream emits 2 again because 2 differs from the previous value, while a distinct stream suppresses it because 2 was already seen at some point.
filter is the plain predicate.
val originalLiveData = MutableLiveData<Int>()
val newLiveData = originalLiveData.filter { it > 2 }
originalLiveData.value = 3 // newLiveData will produce
originalLiveData.value = 2 // newLiveData will not produce thisThe rest of the filtering set is listed rather than exemplified: first() produces a SingleLiveData that produces only one item, take(n) produces only the first n items, takeUntil stops emitting once a predicate is met, skip(n) skips the first n values, skipUntil skips values until a predicate is met and does emit the item that activates it, elementAt(index) emits the item at a position, nonNull() never emits nulls to observers, and defaultIfNull(value) produces the value when null is received.
That last pair is the one that saves the most time in practice, because null handling is the most common place a LiveData consumer has to add a check.
Combining and transforming, and where LiveData breaks the model
The combining operators are the ones that need the most care, and the readme says why.
The warning is in the notes section. Because of the design of LiveData, after a value is emitted to an observer and then another value is emitted, the old value is destroyed in any LiveData object. So unlike RxJava, if a new Observer is attached, it will only receive the most recent value.
That single property changes what concat and then mean. In RxJava, concat(a, b) gives you every value of a followed by every value of b, and a late subscriber to a replayed stream can still get the history. With LiveData, a late subscriber gets only the current value, so a concatenated stream cannot deliver the earlier sequence to someone who subscribes afterwards.
The readme's own conclusion is that if you want to use operators like concat, you have to consider allowing only one observer. The text is cut off there, but the constraint is clear from the mechanism.
The operators themselves follow RxJava semantics. merge takes a list of LiveData and emits anything any of them emits, and there is an instance-level merge for two. concat converts them to SingleLiveData if necessary and emits their first item in order. then concatenates the first LiveData with the given one. startWith emits a starting value first. zip pairs values using a zip function once both have emitted, then emits whenever either does. combineLatest does the same but emits after any of them has emitted. sampleWith samples the current LiveData with another, emitting the last value of the original whenever the other emits.
The transforming set is map, switchMap, doBeforeNext, doAfterNext, buffer(count), scan with an accumulator or with a seed and an accumulator, amb for first to emit, and toMutableLiveData() which converts back to a mutable type with the current value as the initial value.
What this is for, and what the alternatives are
The clearest way to describe Lives is as a compatibility layer for a specific migration path. A team moving from RxJava to LiveData, or a team that adopted LiveData for its lifecycle awareness and kept RxJava for its operators, is the audience.
The alternative approaches are worth naming plainly. The first is to keep RxJava and use its LiveData adapter, which gives you the full RxJava operator set rather than the subset here, at the cost of two reactive libraries in one app. The second is to write the handful of operators you actually need by hand, since most applications use a small fraction of the list, and a map and a filter are not much code. The third is Flow, Kotlin's own reactive stream, which is lifecycle-aware and has the operator vocabulary built in, and which most new Android work uses.
Lives is the right choice in the middle case: an existing codebase that standardised on LiveData, needs a dozen specific operators, and does not want to add RxJava back or migrate to Flow.
It is the wrong choice for a new project, and the version history is why. The listed releases are 2.0.0 from 2020-04-24, 1.3.0 for the AndroidX migration from 2019-04-22, and 1.2.1 from 2019-01-24. There has been no release since 2020, while the repository itself was pushed to on 2026-06-24.
That gap matters for a library whose whole purpose is to be a dependency you compile against. The code may still work, since it depends on AndroidX lifecycle artifacts and the operator implementations are unlikely to have needed changes, but you are depending on an unversioned artifact with no changelog, no release notes and no compatibility statement.
Editorial conclusion
Lives fits a team that standardised on LiveData but kept needing RxJava operators, since the library covers the common cases in one import and works from Java as well as Kotlin. It does not fit a new project, because the listed releases stop at 2.0.0 in April 2020 and the last push was on 2026-06-24 with no recent release. Read the notes section on LiveData destroying old values before you rely on `concat`, and note the dependency documentation is confusing, since the AndroidX and non-AndroidX snippets in the readme appear under swapped headings.
Frequently asked questions
What does the Lives library do?
It adds RxJava-like operators to Android LiveData, usable in Kotlin as extension functions and in Java as static functions on a class called Lives. The operator set covers creation, filtering such as distinct, filter and take, combining such as merge, concat, zip and combineLatest, and transforming such as map, switchMap, scan and buffer.
How do I add Lives to an Android project?
Add the dependency com.snakydesign.livedataextensions:lives, plus the lifecycle-livedata artifact for your AndroidX or pre-AndroidX setup. Java projects also need org.jetbrains.kotlin:kotlin-stdlib, since the Java-facing API is static methods on a Kotlin object.
What is the difference between distinct and distinctUntilChanged in Lives?
distinct emits items that differ from every value emitted so far, so a repeated 2 is suppressed even after a 3 in between. distinctUntilChanged emits items that differ only from the last item, so 2 after 3 is emitted again because it differs from 3.
What is the caveat when using concat with LiveData?
Because of how LiveData is designed, after a value is emitted and then another is emitted, the old value is destroyed. Unlike RxJava, a new observer attaching later only receives the most recent value, so operators like concat require you to allow only one observer.
How do I create a LiveData in Lives from Java?
You call the static methods on the Lives class, for example `Lives.map(Lives.just(2), new Function1<Integer, String>() { ... })`, importing com.snakydesign.livedataextensions.Lives. The Kotlin lambda arguments become Function1 implementations, which makes the Java API considerably more verbose than the Kotlin extension functions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/adibfara-lives)Was this useful?