Skip to content

Commit

Permalink
Closes #20: Zip and CombineLatest crash when null values are posted
Browse files Browse the repository at this point in the history
  • Loading branch information
Sven Obser committed Jun 24, 2019
1 parent df825fa commit 0b53c7e
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 112 deletions.
251 changes: 145 additions & 106 deletions lives/src/main/java/com/snakydesign/livedataextensions/Combining.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ fun <T> LiveData<T>.mergeWith(vararg liveDatas: LiveData<T>): LiveData<T> {
return merge(mergeWithArray)
}


/**
* Merges multiple LiveData, and emits any item that was emitted by any of them
*/
Expand Down Expand Up @@ -56,178 +55,199 @@ fun <T> LiveData<T>.startWith(startingValue: T?): LiveData<T> {

/**
* zips both of the LiveData and emits a value after both of them have emitted their values,
* after that, emits values whenever any of them emits a value.
* after that, emits values whenever both of them emit another value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <T, Y> zip(first: LiveData<T>, second: LiveData<Y>): LiveData<Pair<T, Y>> {
return zip(first, second) { t, y -> Pair(t, y) }
fun <X, Y> zip(first: LiveData<X>, second: LiveData<Y>): LiveData<Pair<X?, Y?>> {
return zip(first, second) { x, y -> Pair(x, y) }
}

fun <T, Y, Z> zip(first: LiveData<T>, second: LiveData<Y>, zipFunction: (T, Y) -> Z): LiveData<Z> {
val finalLiveData: MediatorLiveData<Z> = MediatorLiveData()
/**
* zips both of the LiveData and emits a value after both of them have emitted their values,
* after that, emits values whenever both of them emit another value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, Y, R> zip(first: LiveData<X>, second: LiveData<Y>, zipFunction: (X?, Y?) -> R): LiveData<R> {
val finalLiveData: MediatorLiveData<R> = MediatorLiveData()

val firstEmit: Emit<X?> = Emit()
val secondEmit: Emit<Y?> = Emit()

var firstEmitted = false
var firstValue: T? = null
val combine: () -> Unit = {
if (firstEmit.emitted && secondEmit.emitted) {
val combined = zipFunction(firstEmit.value, secondEmit.value)
firstEmit.reset()
secondEmit.reset()
finalLiveData.value = combined
}
}

var secondEmitted = false
var secondValue: Y? = null
finalLiveData.addSource(first) { value ->
firstEmitted = true
firstValue = value
if (firstEmitted && secondEmitted) {
finalLiveData.value = zipFunction(firstValue!!, secondValue!!)
firstEmitted = false
secondEmitted = false
}
firstEmit.value = value
combine()
}
finalLiveData.addSource(second) { value ->
secondEmitted = true
secondValue = value
if (firstEmitted && secondEmitted) {
finalLiveData.value = zipFunction(firstValue!!, secondValue!!)
firstEmitted = false
secondEmitted = false
}
secondEmit.value = value
combine()
}
return finalLiveData
}


/**
* zips three LiveData and emits a value after all of them have emitted their values,
* after that, emits values whenever any of them emits a value.
* after that, emits values whenever all of them emit another value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <T, Y, X, Z> zip(first: LiveData<T>, second: LiveData<Y>, third: LiveData<X>, zipFunction: (T, Y, X) -> Z): LiveData<Z> {
val finalLiveData: MediatorLiveData<Z> = MediatorLiveData()

var firstEmitted = false
var firstValue: T? = null
fun <X, Y, Z, R> zip(
first: LiveData<X>,
second: LiveData<Y>,
third: LiveData<Z>,
zipFunction: (X?, Y?, Z?) -> R
): LiveData<R> {
val finalLiveData: MediatorLiveData<R> = MediatorLiveData()

var secondEmitted = false
var secondValue: Y? = null
val firstEmit: Emit<X?> = Emit()
val secondEmit: Emit<Y?> = Emit()
val thirdEmit: Emit<Z?> = Emit()

var thirdEmitted = false
var thirdValue: X? = null
finalLiveData.addSource(first) { value ->
firstEmitted = true
firstValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
finalLiveData.value = zipFunction(firstValue!!, secondValue!!, thirdValue!!)
firstEmitted = false
secondEmitted = false
thirdEmitted = false
val combine: () -> Unit = {
if (firstEmit.emitted && secondEmit.emitted && thirdEmit.emitted) {
val combined = zipFunction(firstEmit.value, secondEmit.value, thirdEmit.value)
firstEmit.reset()
secondEmit.reset()
thirdEmit.reset()
finalLiveData.value = combined
}
}

finalLiveData.addSource(first) { value ->
firstEmit.value = value
combine()
}
finalLiveData.addSource(second) { value ->
secondEmitted = true
secondValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
firstEmitted = false
secondEmitted = false
thirdEmitted = false
finalLiveData.value = zipFunction(firstValue!!, secondValue!!, thirdValue!!)
}
secondEmit.value = value
combine()
}

finalLiveData.addSource(third) { value ->
thirdEmitted = true
thirdValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
firstEmitted = false
secondEmitted = false
thirdEmitted = false
finalLiveData.value = zipFunction(firstValue!!, secondValue!!, thirdValue!!)
}
thirdEmit.value = value
combine()
}

return finalLiveData
}

fun <T, Y, X> zip(first: LiveData<T>, second: LiveData<Y>, third: LiveData<X>): LiveData<Triple<T, Y, X>> {
return zip(first, second, third) { t, y, x -> Triple(t, y, x) }
/**
* zips three LiveData and emits a value after all of them have emitted their values,
* after that, emits values whenever all of them emit another value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, Y, Z> zip(first: LiveData<X>, second: LiveData<Y>, third: LiveData<Z>): LiveData<Triple<X?, Y?, Z?>> {
return zip(first, second, third) { x, y, z -> Triple(x, y, z) }
}

/**
* Combines the latest values from two LiveData objects.
* First emits after both LiveData objects have emitted a value, and will emit afterwards after any
* Combines the latest values from multiple LiveData objects.
* First emits after all LiveData objects have emitted a value, and will emit afterwards after any
* of them emits a new value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, T, Z> combineLatest(first: LiveData<X>, second: LiveData<T>, combineFunction: (X, T) -> Z): LiveData<Z> {
val finalLiveData: MediatorLiveData<Z> = MediatorLiveData()
fun <X, Y, R> combineLatest(first: LiveData<X>, second: LiveData<Y>, combineFunction: (X?, Y?) -> R): LiveData<R> {
val finalLiveData: MediatorLiveData<R> = MediatorLiveData()

var firstEmitted = false
var firstValue: X? = null
val firstEmit: Emit<X?> = Emit()
val secondEmit: Emit<Y?> = Emit()

val combine: () -> Unit = {
if (firstEmit.emitted && secondEmit.emitted) {
val combined = combineFunction(firstEmit.value, secondEmit.value)
finalLiveData.value = combined
}
}

var secondEmitted = false
var secondValue: T? = null
finalLiveData.addSource(first) { value ->
firstEmitted = true
firstValue = value
if (firstEmitted && secondEmitted) {
finalLiveData.value = combineFunction(firstValue!!, secondValue!!)
}
firstEmit.value = value
combine()
}
finalLiveData.addSource(second) { value ->
secondEmitted = true
secondValue = value
if (firstEmitted && secondEmitted) {
finalLiveData.value = combineFunction(firstValue!!, secondValue!!)
}
secondEmit.value = value
combine()
}
return finalLiveData
}

/**
* Combines the latest values from multiple LiveData objects.
* First emits after all LiveData objects have emitted a value, and will emit afterwards after any
* of them emits a new value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, Y> combineLatest(first: LiveData<X>, second: LiveData<Y>): LiveData<Pair<X?, Y?>> =
combineLatest(first, second) { x, y -> Pair(x, y) }

/**
* Combines the latest values from two LiveData objects.
* First emits after both LiveData objects have emitted a value, and will emit afterwards after any
* Combines the latest values from multiple LiveData objects.
* First emits after all LiveData objects have emitted a value, and will emit afterwards after any
* of them emits a new value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, Y, T, Z> combineLatest(first: LiveData<X>, second: LiveData<Y>, third: LiveData<T>, combineFunction: (X, Y, T) -> Z): LiveData<Z> {
val finalLiveData: MediatorLiveData<Z> = MediatorLiveData()
fun <X, Y, Z, R> combineLatest(
first: LiveData<X>,
second: LiveData<Y>,
third: LiveData<Z>,
combineFunction: (X?, Y?, Z?) -> R
): LiveData<R> {
val finalLiveData: MediatorLiveData<R> = MediatorLiveData()

var firstEmitted = false
var firstValue: X? = null
val firstEmit: Emit<X?> = Emit()
val secondEmit: Emit<Y?> = Emit()
val thirdEmit: Emit<Z?> = Emit()

var secondEmitted = false
var secondValue: Y? = null
val combine: () -> Unit = {
if (firstEmit.emitted && secondEmit.emitted && thirdEmit.emitted) {
val combined = combineFunction(firstEmit.value, secondEmit.value, thirdEmit.value)
finalLiveData.value = combined
}
}

var thirdEmitted = false
var thirdValue: T? = null
finalLiveData.addSource(first) { value ->
firstEmitted = true
firstValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
finalLiveData.value = combineFunction(firstValue!!, secondValue!!, thirdValue!!)
}
firstEmit.value = value
combine()
}
finalLiveData.addSource(second) { value ->
secondEmitted = true
secondValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
finalLiveData.value = combineFunction(firstValue!!, secondValue!!, thirdValue!!)
}
secondEmit.value = value
combine()
}
finalLiveData.addSource(third) { value ->
thirdEmitted = true
thirdValue = value
if (firstEmitted && secondEmitted && thirdEmitted) {
finalLiveData.value = combineFunction(firstValue!!, secondValue!!, thirdValue!!)
}
thirdEmit.value = value
combine()
}
return finalLiveData
}

/**
* Combines the latest values from multiple LiveData objects.
* First emits after all LiveData objects have emitted a value, and will emit afterwards after any
* of them emits a new value.
*
* The difference between combineLatest and zip is that the zip only emits after all LiveData
* objects have a new value, but combineLatest will emit after any of them has a new value.
*/
fun <X, Y, Z> combineLatest(first: LiveData<X>, second: LiveData<Y>, third: LiveData<Z>): LiveData<Triple<X?, Y?, Z?>> =
combineLatest(first, second, third) { x, y, z -> Triple(x, y, z) }

/**
* Converts the LiveData to `SingleLiveData` and concats it with the `otherLiveData` and emits their
* values one by one
Expand Down Expand Up @@ -280,4 +300,23 @@ fun <T> LiveData<T>.sampleWith(other: LiveData<*>): LiveData<T> {
}
}
return finalLiveData
}

/**
* Wrapper that wraps an emitted value.
*/
private class Emit<T> {

internal var emitted: Boolean = false

internal var value: T? = null
set(value) {
field = value
emitted = true
}

fun reset() {
value = null
emitted = false
}
}
Loading

0 comments on commit 0b53c7e

Please sign in to comment.