LiveDataUtils

Additional

Language
Kotlin
Version
N/A
Created
Apr 29, 2019
Updated
May 16, 2019 (Retired)
Owner
Danny Lamarti (Lamartio)
Contributor
Danny Lamarti (Lamartio)
1
Activity
Badge
Generate
Download
Source code

LiveData utils

When a ViewModel has to work with more complex logic and state, then the default operators of LiveData (map, switchMap and distinctUntilchanged) are not enough. Hence this repo introduces the most popular operators from Rx, but then as extension functions on LiveData.

There is a sample project included for demonstration.

implementation 'io.lamart.livedata:livedata-utils:+'

Creating LiveData

val d1 : MutableLiveData<Int> = mutableLiveDataOf(1)
val d2 : LiveData<Int> = liveDataOf { next ->
    next(1)
    next(2)
    next(3)
}
val d3 : MutableLiveData<Int> = mediatorLiveDataOf {
    addSource(mutableLiveDataOf(), ::setValue)
    addSource(mutableLiveDataOf(), ::postValue)
}

Transforming LiveData

mutableLiveDataOf(1).filter { it % 2 == 0 } // filters the odd numbers

mutableLiveDataOf(1).cast<Int>() // blindly cast each value
mutableLiveDataOf(1).castIf<String>() // cast only when it is possible

mutableLiveDataOf(1).take(3) // only the first three are emitted
mutableLiveDataOf(1).skip(3) // skips the first 3 emissions
mutableLiveDataOf(1).startWith(2) // will emit 2,1

mutableLiveDataOf(1).reduce { acc, value -> acc + value  } // mutate the previous resulte with the current emission
mutableLiveDataOf(1).reduce(seed = "a") { acc: String, value : Int -> acc + value.toString()  } // emits "a1'

Some operators are available via the LiveData KTx library:

mutableLiveDataOf(1)
    .map { it.toFloat() }
    .switchMap { mutableLiveDataOf(it.toString()) }
    .distinctUntilChanged()

The operators below are handy for creating custom operators. Most of the above are created with these.

mutableLiveDataOf(1).wrap { value, next -> next(value) }

mutableLiveDataOf(1).compose { data -> data.map { it.toString() } } 

mutableLiveDataOf(1).lift { next ->
    // hold (mutable) state here
    { value ->
        next(value)
    }
}

Aggregating LiveData

mutableLiveDataOf(1).merge(mutableLiveDataOf(1)) // will emit 1, 1
mutableLiveDataOf(1).combine(mutableLiveDataOf("a")) { num, text -> num.toString() + text } // will emit "1a"

mutableLiveDataOf(1).pair(2) // will emit Pair<Int, Int>(1,2)
mutableLiveDataOf(1).window(3) // will emit a list of 3 whenever it has 3 elements.
mutableLiveDataOf(1).buffer(3) // will emit a list of 3 when it has collected 3 elements. 

Emitting data

val data = mutableLiveDataOf()

data.setValues(1,2,3)
data.setValues(listOf(1,2,3))
data.setValues(sequenceOf(1,2,3))
data.setValues { next ->
    next(1)
    next(2)
    next(3)
}

Observing LiveData

The observe method of LiveData requires Kotlin to always create the Observer functional interface. To replace the interface with a lambda, you can use the subscribe method.

fun example(owner: LifecycleOwner) {
    mutableLiveDataOf(1).observe(owner, Observer { println(it) })
    mutableLiveDataOf(1).subscribe(owner) { println(it) }
}

It is cumbersome the pass a LiveData around, since it requires a LifecycleOwner to make a subscription. To fix this, there is an overload that returns a function that can be called without the need of a LifecycleOwner.

fun example1(owner: LifecycleOwner) {
    val observe: Observe<Int> = mutableLiveDataOf(1).observe(owner)

    // ... somewhere else the Observer can be supplied    
    observe(Observer { println(it) })
}

fun example2(owner: LifecycleOwner) {
    val subscribe: Subscribe<Int> = mutableLiveDataOf(1).subscribe(owner)

    // ... somewhere else the lambda can be supplied
    subscribe(::println)
}

This can be very useful for passing observable data to a ViewModel in the case of events like clicks or text changes. In the sample project are buttons that sort the ViewModel data by using subscribe and combine.

Logging

There are operators for executing a side effect before and after calling the subsequent operators.

mutableLivedataOf(1).beforeEach { println(it) } 
mutableLivedataOf(1).afterEach { println(it) } 

Posting data

The post operator will execute all subsequent operation on the main thread.

mutableLivedataOf(1).post().map { number -> number.toString() }

The overload will call post after the given callback.

mutableLivedataOf(1).post { data -> data.map { number -> number.toString() } }

Acknowledgement

Many thanks to Josef Raska for making LiveData tests a breeze.

License

Copyright 2019 Danny Lamarti

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.