将多个 LiveData 值映射为一个

Posted

技术标签:

【中文标题】将多个 LiveData 值映射为一个【英文标题】:Map multiple LiveData values into one 【发布时间】:2018-01-24 14:59:51 【问题描述】:

因此,在ios 开发中,我使用ReactiveCocoa,并且通过该框架,我能够观察多个NSObjects 并将它们组合成一个返回a 值的signal。像这样的:

-(RACSignal *)modelIsValidSignal 

    return [RACSignal combineLatest:@[RACObserve(self,username), RACObserve(self,password), RACObserve(self, busyLoggingIn)]
                             reduce:^id(NSString *username, NSString *password, NSNumber *busyLoggingIn) 
                                 return @((username.length > 0) && (password.length > 0 && busyLoggingIn.boolValue == NO));
                             ];

因此,这将返回一个 boolean,它要么为假要么为真。一旦其中一个对象状态发生变化,就会通知该信号,然后subscriber (Observer) 将获得该布尔值的当前值。

如何使用LiveData 做类似的事情?最接近这样做的是MediatorLiveData,但是我不知道如何同时观察多个LiveData 事件然后减少它,就像上面的例子一样。

【问题讨论】:

LiveData 专为相当简单的场景而设计。如果你需要这样的东西,请使用 RxJava。 我有点担心将 RxJava 加入其中;因为我试图摆脱对第三部分库的依赖。那么,您是说 LiveData 不可能做到这一点? “我正在尝试减少对第三部分库的依赖”——LiveData 来自一个库。所有的 android 开发都依赖于库。几乎所有现代软件开发都依赖于库。 “所以,你是说 LiveData 不可能做到这一点?” - 没有什么“开箱即用”可以做到这一点。 MediatorLiveData 支持 N 个源,因此您可以进行所需的转换。我的观点是,谷歌已经表明 RxJava 是复杂反应流的答案。 查看***.com/a/57819928/4685284 【参考方案1】:

我已经做了一些这样的验证

var firstName: MutableLiveData<String> = MutableLiveData()
var lastName: MutableLiveData<String> = MutableLiveData()

var personalDetailsValid: MediatorLiveData<Boolean> = MediatorLiveData()

personalDetailsValid.addSource(firstName, 
    personalDetailsValid.value = lastName.value?.isNotBlank() == true && it?.isNotBlank() ?: false
)

personalDetailsValid.addSource(lastName, 
    personalDetailsValid.value = firstName.value?.isNotBlank() == true && it?.isNotBlank() ?: false
)

那我就按平常的方式观察

viewModel.personalDetailsValid.observe(this, Observer 
        if (it == true) 
            //Do something
        
    )

我不知道这是否是使用 LiveData 和 MediatorLiveData 的推荐方式,但它对我有用。

【讨论】:

这个案例怎么样:假设我们有两个不同的 LiveData 源。 F.e.我们不知道哪个来源将首先发布数据。此外,我们需要每个来源都知道另一个来源发布的价值。我想必须有一些解决方案。 如您所见,我有两个单独的来源,我分别添加它们来处理这种情况。我们添加了 firstName 的来源,因此对这个变量的任何更改都会得到验证,但也会与 lastName 变量一起使用。同样适用于 lastNameVariable。还是我在你的问题中遗漏了一些东西?【参考方案2】:

方便又安全的解决方案:

fun <T1, T2, R> combine(
    liveData1: LiveData<T1>,
    liveData2: LiveData<T2>,
    combineFn: (value1: T1?, value2: T2?) -> R
): LiveData<R> = MediatorLiveData<R>().apply 
    addSource(liveData1) 
        value = combineFn(it, liveData2.value)
    
    addSource(liveData2) 
        value = combineFn(liveData1.value, it)
    


fun <T1, T2, R> Pair<LiveData<T1>, LiveData<T2>>.map(
    combineFn: (value1: T1?, value2: T2?) -> R
) = combine(first, second, combineFn)

使用示例:

val greetLine = combine(nameLiveData, surnameLiveData)  name, surname ->
   "Hello, $name $surname!"


// or using map extension function for consistency with LiveData api
val greetLine = (nameLiveData to surnameLiveData).map  name, surname ->
   "Hello, $name $surname!"

【讨论】:

【参考方案3】:

我已经为 2 个(以及更多)LiveData 值实现了一些实用方法。以 RX Observable.combineLatest() 方式工作,这意味着如果观察到的 LiveData 之一将发出任何新值,它将触发更新的值。我已将 Function2 添加为“组合器”函数(在我的情况下,我可以轻松使用 RX.BiFunction,但 LiveData 可以发出 null 值)。

组合来自两个LiveData 的值的示例。

object LiveDataUtils 

    fun <T1, T2, R> combineLatest(source1: LiveData<T1>,
                                  source2: LiveData<T2>,
                                  combiner: Function2<T1, T2, R>): LiveData<R> 
        val mediator = MediatorLiveData<R>()

        val combinerFunction = 
            val source1Value = source1.value
            val source2Value = source2.value
            mediator.value = combiner.apply(source1Value, source2Value)
        

        mediator.addSource(source1)  combinerFunction() 
        mediator.addSource(source2)  combinerFunction() 
        return mediator
    



interface Function2<T1, T2, R> 
    fun apply(t1: T1?, t2: T2?): R

【讨论】:

以上是关于将多个 LiveData 值映射为一个的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack之LiveData扩展MediatorLiveData

Android LiveData粘性,粘连,倒灌

具有多个不同类型来源的 LiveData

将一个值映射到JPA中的多个列

Android MVVM:具有多个片段的活动 - 将共享 LiveData 放在哪里?

如何将editText值传递给viewModel和Livedata(Kotlin)