【问题标题】:How to avoid all sources of a MediatorLiveData calling the same function?如何避免 MediatorLiveData 的所有来源调用相同的函数?
【发布时间】:2019-08-06 16:44:31
【问题描述】:

问题简介: 当使用 android 与 android jetpack 添加的MediatorLiveData 时,我发现自己经常从每个来源调用相同的函数。例如,这可能是因为,每当更新源时,我必须检查它是否有影响,或者另一个源是否更重要。代码中的示例(Kotlin,但不重要):

val isHovered = MutableLiveData<Boolean>()
val isSelected = MutableLiveData<Boolean>()

val color = MediatorLiveData<Int>().apply { 
    addSource(isHovered) { updateColor() }
    addSource(isSelected) { updateColor() }
}

fun updateColor() {
    if (isHovered.value == true)
        color.value = Color.GREEN
    else if (isSelected.value == true)
        color.value = Color.RED
    else
        color.value = Color.GRAY
}

悬停时该项目为绿色,选中时为红色,未悬停时为灰色,否则为灰色。当 isSelected 更改为 true 时,我仍然需要检查它是否悬停,然后再将颜色更改为红色。此外,当 isHovering 更改为 false 时,我需要在将颜色更改为灰色之前检查它是否被选中。所以最简单的是一个函数,它考虑了所有变量并相应地设置颜色。

我的问题: 当 MediatorLiveData 从非活动状态变为活动状态时,由于视图被移至前台,函数updateColor 可能会针对每个发生变化的源被多次调用。这是不必要的,因为每次调用都已经考虑了所有变量。由于此函数可能非常复杂,并且可能有很多来源,有没有办法避免多次调用它来为源 LiveDatas 的相同状态?

【问题讨论】:

  • 为什么需要apply函数?
  • @IgorGanapolsky 这是一个 Kotlin 的东西,它允许我直接使用一个对象,同时也返回它。您也可以从构造函数中调用addSource 函数。

标签: android android-livedata android-jetpack event-driven


【解决方案1】:

我遇到了同样的问题,并提出了以下解决方案。首先,将所有来源的值收集到一个值:

data class State(val isHovered: Boolean, val isSelected: Boolean)

private val state = MediatorLiveData<State>().apply {
    fun update() {
        value = State(isHovered.value ?: false, isSelected.value ?: false)
    }

    addSource(isHovered) { update() }
    addSource(isSelected) { update() }
}

然后我创建了一个扩展函数来只发出不同的值

fun <T> LiveData<T>.distinct(): LiveData<T> = MediatorLiveData<T>().apply {
    var wasSet = false
    addSource(this@distinct) {
        // Update value if it has been changed or it's the first time the method called
        // Because value is null by default it will not be set to null again, leaving the live data in [value not set] state
        if (value != it || !wasSet) {
            value = it
            wasSet = true
        }
    }
}

因此,当观察state.distinct() 时,只有当它真正改变时,你才会得到更新的值。

有时另一个扩展也很有用:

fun <T> LiveData<T>.squashUpdates() = MediatorLiveData<T>().apply {
    addSource(this@squashUpdates) {
        postValue(it)
    }
}

通常,当LiveData 的值发生变化时,它会立即通知同一堆栈帧中的所有观察者。假设您有一个 MediatorLiveData 并更改多个来源的值,例如:

isHovered.value = true
isSelected.value = true

state 值将连续更改两次 State(true, false)State(true, true)。即使从上面使用distinct,它们也会被调度,因为值实际上是不同的。 squashUpdates 在这种情况下有助于延迟调度到堆栈帧的末尾,仅调度最后一个值。

【讨论】:

    【解决方案2】:

    为避免多次调用,您应该只发送来自您的 LiveData 实例的新更新。为此,您可以使用SingleLiveEvent

    一个生命周期感知的可观察对象,在之后只发送新的更新 订阅,用于导航和 Snackbar 消息等事件。

    这避免了事件的一个常见问题:配置更改 如果观察者处于活动状态,则可以发出(如旋转)更新。这个 LiveData 仅在有显式调用时才调用 observable setValue() 或 call()。

    请注意,只有一名观察者会收到更改通知。

    【讨论】:

    • SingleLiveEvent 是我正在使用的一种模式,但是如果所有源 LiveDatas 都有新值,它就不起作用。通用函数仍然会被过于频繁地调用。
    【解决方案3】:

    您可能无法阻止isHoveredisSelected 发出项目,但您可以阻止color 发出多个相同类型的项目。这个想法是仅在当前颜色不等于新颜色时才更改颜色。

    fun updateColor() {
        var newColor: Color?    
    
        if (isHovered.value == true)
            newColor = Color.GREEN
        else if (isSelected.value == true)
            newColor = Color.RED
        else
            newColor = Color.GRAY
    
        if (color.value != newColor) {
            color.value = newColor!!
        }
    }
    

    【讨论】:

    • 我明白了。我已经有一个辅助函数,仅在实际更改时才更新。但这并不能阻止昂贵的函数运行两次
    猜你喜欢
    • 1970-01-01
    • 2014-11-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 2016-11-21
    • 2012-12-08
    • 2018-12-06
    相关资源
    最近更新 更多