具有多个参数的 LiveData Transformations.map()
LiveData Transformations.map() with multiple arguments
我在 UI 中有一个值,它的值取决于两个 LiveData objects. Imagine a shop where you need a subtotal = sum of all items price
and a total = subtotal + shipment price
. Using Transformations 我们可以对小计 LiveData 对象执行以下操作(因为它仅取决于 itemsLiveData
):
val itemsLiveData: LiveData<List<Items>> = ...
val subtotalLiveData = Transformations.map(itemsLiveData) {
items ->
getSubtotalPrice(items)
}
在总数的情况下,能够做这样的事情会很棒:
val shipPriceLiveData: LiveData<Int> = ...
val totalLiveData = Transformations.map(itemsLiveData, shipPriceLiveData) {
items, price ->
getSubtotalPrice(items) + price
}
但是,不幸的是,这是不可能的,因为我们不能在 map 函数中放置多个参数。有人知道实现此目标的好方法吗?
更新
根据我之前的回答,我创建了一种通用方法,我们可以根据需要添加任意数量的实时数据。
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
/**
* CombinedLiveData is a helper class to combine results from multiple LiveData sources.
* @param liveDatas Variable number of LiveData arguments.
* @param combine Function reference that will be used to combine all LiveData data.
* @param R The type of data returned after combining all LiveData data.
* Usage:
* CombinedLiveData<SomeType>(
* getLiveData1(),
* getLiveData2(),
* ... ,
* getLiveDataN()
* ) { datas: List<Any?> ->
* // Use datas[0], datas[1], ..., datas[N] to return a SomeType value
* }
*/
class CombinedLiveData<R>(vararg liveDatas: LiveData<*>,
private val combine: (datas: List<Any?>) -> R) : MediatorLiveData<R>() {
private val datas: MutableList<Any?> = MutableList(liveDatas.size) { null }
init {
for(i in liveDatas.indices){
super.addSource(liveDatas[i]) {
datas[i] = it
value = combine(datas)
}
}
}
}
旧
最后我用MediatorLiveData实现了同样的objective。
fun mapBasketTotal(source1: LiveData<List<Item>>, source2: LiveData<ShipPrice>): LiveData<String> {
val result = MediatorLiveData<String>()
uiThread {
var subtotal: Int = 0
var shipPrice: Int = 0
fun sumAndFormat(){ result.value = format(subtotal + shipPrice)}
result.addSource(source1, { items ->
if (items != null) {
subtotal = getSubtotalPrice(items)
sumAndFormat()
}
})
result.addSource(source2, { price ->
if (price != null) {
shipPrice = price
sumAndFormat()
}
})
}
return result
}
我想出了另一个解决方案。
class PairLiveData<A, B>(first: LiveData<A>, second: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() {
init {
addSource(first) { value = it to second.value }
addSource(second) { value = first.value to it }
}
}
class TripleLiveData<A, B, C>(first: LiveData<A>, second: LiveData<B>, third: LiveData<C>) : MediatorLiveData<Triple<A?, B?, C?>>() {
init {
addSource(first) { value = Triple(it, second.value, third.value) }
addSource(second) { value = Triple(first.value, it, third.value) }
addSource(third) { value = Triple(first.value, second.value, it) }
}
}
fun <A, B> LiveData<A>.combine(other: LiveData<B>): PairLiveData<A, B> {
return PairLiveData(this, other)
}
fun <A, B, C> LiveData<A>.combine(second: LiveData<B>, third: LiveData<C>): TripleLiveData<A, B, C> {
return TripleLiveData(this, second, third)
}
然后,您可以合并多个来源。
val totalLiveData = Transformations.map(itemsLiveData.combine(shipPriceLiveData)) {
// Do your stuff
}
如果您想拥有 4 个或更多来源,您需要创建自己的数据 class 因为 Kotlin 只有 Pair
和 Triple
.
在我看来,在 Damia 的解决方案中没有理由 运行 和 uiThread
。
对于这种情况,您可以使用 switchMap(),因为它 returns LiveData 对象可以是 Transformations.map()
在下面的代码中,我得到了两个对象 onwardSelectQuote 和 returnSelectQuote
的最终数量之和
finalAmount = Transformations.switchMap(onwardSelectQuote) { data1 ->
Transformations.map(returnSelectQuote) { data2 -> ViewUtils.formatRupee((data1.finalAmount!!.toFloat() + data2.finalAmount!!.toFloat()).toString())
}
}
我在 UI 中有一个值,它的值取决于两个 LiveData objects. Imagine a shop where you need a subtotal = sum of all items price
and a total = subtotal + shipment price
. Using Transformations 我们可以对小计 LiveData 对象执行以下操作(因为它仅取决于 itemsLiveData
):
val itemsLiveData: LiveData<List<Items>> = ...
val subtotalLiveData = Transformations.map(itemsLiveData) {
items ->
getSubtotalPrice(items)
}
在总数的情况下,能够做这样的事情会很棒:
val shipPriceLiveData: LiveData<Int> = ...
val totalLiveData = Transformations.map(itemsLiveData, shipPriceLiveData) {
items, price ->
getSubtotalPrice(items) + price
}
但是,不幸的是,这是不可能的,因为我们不能在 map 函数中放置多个参数。有人知道实现此目标的好方法吗?
更新
根据我之前的回答,我创建了一种通用方法,我们可以根据需要添加任意数量的实时数据。
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
/**
* CombinedLiveData is a helper class to combine results from multiple LiveData sources.
* @param liveDatas Variable number of LiveData arguments.
* @param combine Function reference that will be used to combine all LiveData data.
* @param R The type of data returned after combining all LiveData data.
* Usage:
* CombinedLiveData<SomeType>(
* getLiveData1(),
* getLiveData2(),
* ... ,
* getLiveDataN()
* ) { datas: List<Any?> ->
* // Use datas[0], datas[1], ..., datas[N] to return a SomeType value
* }
*/
class CombinedLiveData<R>(vararg liveDatas: LiveData<*>,
private val combine: (datas: List<Any?>) -> R) : MediatorLiveData<R>() {
private val datas: MutableList<Any?> = MutableList(liveDatas.size) { null }
init {
for(i in liveDatas.indices){
super.addSource(liveDatas[i]) {
datas[i] = it
value = combine(datas)
}
}
}
}
旧
最后我用MediatorLiveData实现了同样的objective。
fun mapBasketTotal(source1: LiveData<List<Item>>, source2: LiveData<ShipPrice>): LiveData<String> {
val result = MediatorLiveData<String>()
uiThread {
var subtotal: Int = 0
var shipPrice: Int = 0
fun sumAndFormat(){ result.value = format(subtotal + shipPrice)}
result.addSource(source1, { items ->
if (items != null) {
subtotal = getSubtotalPrice(items)
sumAndFormat()
}
})
result.addSource(source2, { price ->
if (price != null) {
shipPrice = price
sumAndFormat()
}
})
}
return result
}
我想出了另一个解决方案。
class PairLiveData<A, B>(first: LiveData<A>, second: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() {
init {
addSource(first) { value = it to second.value }
addSource(second) { value = first.value to it }
}
}
class TripleLiveData<A, B, C>(first: LiveData<A>, second: LiveData<B>, third: LiveData<C>) : MediatorLiveData<Triple<A?, B?, C?>>() {
init {
addSource(first) { value = Triple(it, second.value, third.value) }
addSource(second) { value = Triple(first.value, it, third.value) }
addSource(third) { value = Triple(first.value, second.value, it) }
}
}
fun <A, B> LiveData<A>.combine(other: LiveData<B>): PairLiveData<A, B> {
return PairLiveData(this, other)
}
fun <A, B, C> LiveData<A>.combine(second: LiveData<B>, third: LiveData<C>): TripleLiveData<A, B, C> {
return TripleLiveData(this, second, third)
}
然后,您可以合并多个来源。
val totalLiveData = Transformations.map(itemsLiveData.combine(shipPriceLiveData)) {
// Do your stuff
}
如果您想拥有 4 个或更多来源,您需要创建自己的数据 class 因为 Kotlin 只有 Pair
和 Triple
.
在我看来,在 Damia 的解决方案中没有理由 运行 和 uiThread
。
对于这种情况,您可以使用 switchMap(),因为它 returns LiveData 对象可以是 Transformations.map()
在下面的代码中,我得到了两个对象 onwardSelectQuote 和 returnSelectQuote
的最终数量之和finalAmount = Transformations.switchMap(onwardSelectQuote) { data1 ->
Transformations.map(returnSelectQuote) { data2 -> ViewUtils.formatRupee((data1.finalAmount!!.toFloat() + data2.finalAmount!!.toFloat()).toString())
}
}