前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LiveData 非粘性消息的探索和尝试

LiveData 非粘性消息的探索和尝试

作者头像
GeeJoe
发布2021-12-09 12:18:46
8900
发布2021-12-09 12:18:46
举报
文章被收录于专栏:掘金文章

LiveData 默认是支持粘性消息的(关于什么是粘性消息,请移步我的另一篇文章:LiveData 的正确使用姿势以及反模式 ),如何通过 LiveData 来实现非粘性消息呢,本文将在官博的基础上,分析几种尝试的方案,以及他们各自的优缺点

姿势一:重置 LiveData 的值

在 observer 里加上一个判断,当 LiveData 的值符合某个条件的时候,才做出响应的更新 UI 逻辑,然后提供一个重置 LiveData 值的方法,重置之后,observer 中条件判断为 fasle,因此可以达到不更新 UI 的目的

示例代码

代码语言:javascript
复制
moneyReceivedViewModel.billLiveData.observe(this, Observer {
	if (it != null) {
		Toast.makeText(this, "到账$it元", Toast.LENGTH_SHORT).show()
	}
})
复制代码
代码语言:javascript
复制
class MoneyReceivedViewModel : ViewModel {
	private val _billLiveData = MutableLiveData<String>()
	val billLiveData: LiveData<String> = _billLiveData
	
	// 在 observe 之前和 show Toast 之后重置一下 LiveData
	fun reset() {
		_billLiveData.value = null
	}
}
复制代码

缺陷:

  1. 需要在 observer 中增加一些逻辑判断代码,这不符合简洁的 MVVM 模式(不应该在 View 层做过多的逻辑处理)
  2. 需要手动重置,不够优雅,一旦忘记重置就容易引发问题

姿势二:使用 SingleLiveEvent

SingleLiveEvent 是官方 sample 中封装的 LiveData,可以实现一个事件只被消费一,实现原理也很简单

代码语言:javascript
复制
class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val mPending: AtomicBoolean = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(
                TAG,
                "Multiple observers registered but only one will be notified of changes."
            )
        }

        // Observe the internal MutableLiveData
        super.observe(owner, object : Observer<T?> {
            override fun onChanged(t: T?) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t)
                }
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        setValue(null)
    }

    companion object {
        private const val TAG = "SingleLiveEvent"
    }
}
复制代码

示例代码

代码语言:javascript
复制
class MoneyReceivedViewModel : ViewModel() {
    val billLiveEvent = SingleLiveEvent<String>()

    fun payForLiveEvent(money: String) {
        billLiveEvent.value = money
    }
}
复制代码
代码语言:javascript
复制
viewModel.payForLiveEvent("100")

viewModel.billLiveEvent.observe(this, Observer {
    Log.d("sample", "到账 ${it} 元")
})

btn.setOnClickListener {
    viewModel.payForLiveEvent("200")
}

btn_wait.setOnClickListener {
    viewModel.billLiveEvent.observe(this, Observer {
        Log.d("sample", "到账 ${it} 元")
    })
}

// 以上代码在 Activity 的 onCreate() 中
// 当 Activity 启动的时候就会输出日志 -> 到账 100 元(没有实现不接收 observe() 之前的事件)
// 点击 btn 之后输出 -> 到账 200 元
// 点击 btn_wait 无输出(实现了事件只被消费一次)
复制代码

缺陷:

  1. 由于 setValue() 之后 onChange() 只会回调一次,因此,如果有多个 observer 的话,也只有一个才能收到回调,而且无法保证哪一个 observer 被回调(每个 observer 生命周期不一样,observe() 时机也不一样)
  2. 在 observe 之前发送的事件还是会被接收到,没有解决问题

姿势三:LiveData 包裹一个 Event

代码语言:javascript
复制
open class Event<out T>(private val content: T) {

    var hasBeenConsumed = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun consumed(): T? {
        return if (hasBeenConsumed) {
            null
        } else {
            hasBeenConsumed = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peek(): T = content
}
复制代码
代码语言:javascript
复制
class MoneyReceivedViewModel : ViewModel() {
    private val _billEvent = MutableLiveData<Event<String>>()

    val billEvent: LiveData<Event<String>>
        get() = _billEvent

    fun payForEvent(msg: String) {
        _billEvent.value = Event(msg)
    }
}
复制代码
代码语言:javascript
复制
viewModel.payForEvent("100")

viewModel.billEvent.observe(this, Observer {
		it.consumed()?.let {
			Log.d("sample", "到账 ${it} 元")
		}
})

btn.setOnClickListener {
    viewModel.payForEvent("200")
}

btn_wait.setOnClickListener {
	  viewModel.billEvent.observe(this, Observer {
			it.consumed()?.let {
				Log.d("sample", "到账 ${it} 元")
			}
		})
}

// 以上代码在 Activity 的 onCreate() 中
// 当 Activity 启动的时候就会输出日志 -> 到账 100 元(没有实现不接收 observe() 之前的事件)
// 点击 btn 之后输出 -> 到账 200 元
// 点击 btn_wait 无输出(实现了事件只被消费一次)
复制代码

这种方式的好处是:

  • onChanged() 每次都会回调,但是是否要处理数据取决于 observer:consumed() 不返回已经被消费的消息,peek() 可返回已经被消费的数据

缺陷:

  1. 和姿势二一样,observe() 之前的数据还是会被监听到,没有解决问题
  2. 虽然可以添加多个 observers 且使用 peek() 来获取数据,但是还是无法实现多个 observer 都只接收一次事件

姿势四:支持多 observer 且仅接受 observe() 之后的消息

可参考 基于LiveData实现事件总线思路和方案

LiveData 并不是非用不可

我们使用了各种 workaround 的方式让 LiveData 支持粘性消息,以上几种方案也只有最后一种能够解决问题。但是笔者并不推荐使用这样的方式来绕过 LiveData 的限制,去打破 LiveData 原本的设计,这会让 LiveData 变得更让人难以理解

我们并不是非要用 LiveData 不可,LiveData 有适合自己的使用场景(具体可移步:LiveData 的正确使用姿势以及反模式 ),事件总线的场景已经有非常多的优秀开源库可以使用:EventBus、RxBus 等都可以供我们参考。

另一篇官博也提到了,如果我们项目中已经有一些比较成熟的方案,我们大可不必非要使用 LiveData

LiveData and RxJava Finally, let’s address the elephant in the room. LiveData was designed to allow the View observe the ViewModel. Definitely use it for this! Even if you already use Rx, you can communicate both with LiveDataReactiveStreams*. If you want to use LiveData beyond the presentation layer, you might find that MediatorLiveData does not have a toolkit to combine and operate on streams of data like RxJava offers. However, Rx comes with a steep learning curve. A combination of LiveData transformations (and Kotlin magic) might be enough for your case but if you (and your team) already invested in learning RxJava, you probably don’t need LiveData. *If you use auto-dispose, using LiveData for this would be redundant.

这里有些人可能会提到现有的 EventBus 或者 RxBus 等都没有生命周期感知能力,不能在生命周期销毁的时候自动解绑监听,而 LiveData 有这个能力,所以想使用 LiveData 来实现事件总线。这里其实我们可以换一种思路:给 callback 或者 EventBus 等增加 Lifecycle 感知能力,这样便能实现自动解绑了,这种方式相比使用 hack 的手段修改 LiveData 会更加友好一些,具体可以参考我的另一篇文章:自定义生命周期以及实现生命周期感知能力


相关文章

使用 Architecture Component 实现 MVVM 的正确姿势

LiveData 的正确使用姿势以及反模式

自定义生命周期以及实现生命周期感知能力

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年04月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 姿势一:重置 LiveData 的值
  • 姿势二:使用 SingleLiveEvent
  • 姿势三:LiveData 包裹一个 Event
  • 姿势四:支持多 observer 且仅接受 observe() 之后的消息
  • LiveData 并不是非用不可
相关产品与服务
事件总线
腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档