翻译自:

https://arkadiuszchmura.com/posts/be-careful-when-converting-flow-to-livedata/

1

介绍

最近我在负责一段代码库,需要在使用 Flow 的 Data 层和仍然依赖 LiveData 暴露 State 数据的 UI 层之间实现桥接。好在 androidx.lifecycle 框架已经提供了一个叫做 asLiveData() 的方法,可以让你毫不费力地将 Flow 转为 LiveData。

然而使用这种方式得到的 LiveData 需要牢记一点:在拥有一个及以上活跃的观察者的条件下,它才会发射数据。假使上游的 flow 产生了更新,但对应的 LiveData 并非活跃的状态,那么它将无法获得最新的数值。

让我通过如下的实例,向你展示我们可能会遇到的这种潜在问题。

2

示例

我们有一个简单的 Activity,它持有 AAC ViewModel 的实例:


class MainActivity : AppCompatActivity() {  private val viewModel: MainViewModel by viewModels()  override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  setContentView(R.layout.activity_main)    }
}

该 ViewModel 的实现是这样的:


class MainViewModel : ViewModel() {  private val repository = Repository()  val state: LiveData<Int> = repository.state.asLiveData()
}

它持有一个 Repository 实例,充当琐碎的数据层。

同时 ViewModel 还通过前面提到的 asLiveData() 方法,将 Repository 持有的 StateFlow 转为了 LiveData 并对外暴露了其 State 数据。

Repository 的实现如下:


class Repository {  private val _state = MutableStateFlow(-1)  val state: StateFlow<Int> = _state  suspend fun update() {  _state.emit(Random.nextInt(until = 1000))  }
}

它拥有一个包裹着 Integer 数据(初始值为 -1)的 StateFlow 示例,同时对外提供了一个方法允许外界更新它的 State:从 0 到 1000 之间取得一个新的随机数。

试想一下,假使希望 Activity 创建的时候就能执行这个数据更新。我们可以这么实现:

  1. 在 MainViewModel 内创建一个 init() 来做这个操作。

  2. Activity 的onCreate() 里调用该方法。


// MainViewModel
fun init() {// update() is suspending, so we launch a new coroutine hereviewModelScope.launch {  repository.update()}
}// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  setContentView(R.layout.activity_main)  viewModel.init()
}

这样的话,Activity 创建的时候一个新的协程将被启动,最终会调用 Repository 的 update(),生成一个随机数并发射到它的 State。

此外,我们可能还需要在 ViewModel 中去发送包含了新生成数值的事件出去。可以在 ViewModel 中添加一个sendAnalyticalEvent() ,这样可以在执行完 Repository 的 update()之后立即调用它。


// MainViewModel
fun init() {  viewModelScope.launch {  repository.update()  sendAnalyticalEvent() // <-- NEW}
}  private fun sendAnalyticalEvent() {  // Typically, we would schedule a network request here  val liveDataValue = state.value  val flowValue = repository.state.value  Log.d("Current number in LiveData", "$liveDataValue")  Log.d("Current number in StateFlow", "$flowValue")
}

该方法内,我们可以做些典型的操作,比如向后端服务器发送网络请求。这里,让我们仅仅在 Logcat 里打印来自 LiveData and Flow 的数值即可。

上面的运行结果相当出乎意料。你可能会争辩道:LiveData 没有获取到最新的数值,是因为没有足够的时间从上游的 flow 中收集数据,不然的话肯定能够拿到正确的数值。

但这个 case 里,不仅仅是 LiveData 获得到的是错误的数值,它获得到的是 null。而且请别忘了,它的存放在 Repository 里的初值是 -1。这只能代表一个意思:这里的 LiveData 压根没有从 StateFlow 里收集任何数据。

原因是我们还没有开始观察这个 LiveData,它自然会被当作是非活跃的。而且根据 asLiveData() 方法的文档可以知道,在这种情况下 LiveData 不会从上游的 flow 收集任何数据。

asLiveData:Creates a LiveData that has values collected from the origin Flow.

上游 flow 数据的收集发生在 LiveData 变成活跃的时候,即 LiveData.onActive。如果 flow 尚未完成,而 LiveData 变成了非激活状态,即 LiveData.onActive,那么 flow 的数据收集将在timeoutInMs 参数指定的时间后被取消。除非在超时之前,LiveData 变成活跃状态。

一旦我们开始在 Activity 里观察 LiveData 的数据(因此将促使 LiveData 变成活跃状态),它就能够拥有正确的、最新的数值了。


// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  setContentView(R.layout.activity_main)  viewModel.init()  viewModel.state.observe(this) { // <-- NEW  Log.d("Current number in MainActivity", "$it")  }
}

如下是 Logcat 里新的输出。

上面的示例里,我们采用的是 StateFlow,但规则同样适用于 SharedFlow。

而且,情况将更加糟糕,因为当 LiveData 处于非激活状态的时候,任何发送给 SharedFlow的事件都将永久丢失(默认情况下 SharedFlow 不会将任何数值重新发送给新的订阅者)。

3

总结

请时刻记住采用 asLiveData() 方法转换 Flow 得到的 LiveData 将会和预期的稍稍不同:它只会在注册了活跃观察者的情况下发射数据。

就我个人而言,这种行为无可厚非:因为我们都还没有观察它、自然不会在意 LiveData 的数值是啥、能不能获取得到。但话说回来,确实存在一些场景,需要在你尚未开始观察的时候,去访问 ViewModel 中 LiveData 的当前数值。

通过阅读这篇文章,我希望你在遇到这种获取不到正确数值的情况时,不要惊讶、心中有数。

转自:Flow 转 LiveData 后数据丢了,肿么回事?

Flow 转 LiveData 后数据丢了,肿么回事?相关推荐

  1. Google官方推荐 Flow 取代 LiveData

    1. LiveData有什么不足? 1.1 为什么引入LiveData? 要了解LiveData的不足,我们先了解下LiveData为什么被引入 LiveData 的历史要追溯到 2017 年.彼时, ...

  2. K8S集群中Pod资源数据丢包排查思路

    K8S集群中Pod资源数据丢包排查思路 Pod资源可能会由于网络原因产生丢包的现象. 当Pod资源存在丢包的现象时,会出现下面的报错: Connect to 100.111.156.74 port 5 ...

  3. 利用mininet进行链路拥塞造成数据丢包的实验

    实验原理 网络链路拥塞是指在分组交换网络中传送分组的数目太多时,由于存储转发节点的资源有限而造成网络传输性能下降的情况.当网络发生拥塞时,一般会出现数据丢失,时延增加,吞吐量下降,严重时甚至会导致&q ...

  4. 数据丢包怎么修复_一种网络传输中实时音频数据丢包恢复的方法与流程

    本发明涉及通信技术领域,具体涉及一种网络传输中实时音频数据丢包恢复的方法. 背景技术: 随着通信技术的发展,音频传输系统对实时性和准确性的要求越来越高.在网络的音频传输过程中,影响音频音质的主要因素是 ...

  5. 服务器硬盘数据丢了怎么恢复,服务器数据丢了怎么恢复

    服务器数据丢了怎么恢复 浏览量: 0 次  来源:未知  发布日期:2018-03-19 21:33:21 服务器数据恢复主要包括两个方面: (1) 服务器单个或多个硬盘出现故障: 服务器硬盘经常使用 ...

  6. 如何保证云docker容器重启后数据不丢失

    由于测试服,经常会重启docer,而每次重启数据将会丢失mysql数据.现在需要持久化,持久化不能用docker内部,只能用外部磁盘.本章教你咋解决这个问题. 挂载到PVC磁盘中(这个要钱) 另外就是 ...

  7. 硬盘数据丢了怎么恢复?一分钟学会数据恢复

    硬盘数据丢了怎么恢复?硬盘是电脑中最为重要的一个存储设备,里面存放着操作系统.应用程序.个人手机等等内容,一旦丢失都会给我们带来一定的困扰,那造成硬盘数据丢失的原因有哪些?数据又该如恢复?下面就一起来 ...

  8. Udp数据丢包测试--iperf3

    1.背景 近期需要用UDP来传输大量数据,为了避免出现大量丢包,所以需要对UDP数据丢包进行测试.推动开发减少丢包率. 2.环境 目前demo测试使用的环境是windows环境(后期如有要求可以改为l ...

  9. python中numpy数组和字符串互转(互转后数据完全一致)

    python中numpy数组和字符串互转(互转后数据完全一致) 代码: import numpy as np # 创建数组arr arr = np.array([ -1.8264365 , -0.63 ...

最新文章

  1. python将二维列表内容写入和读取.txt文件
  2. 解决从本地文件系统上传到HDFS时的权限问题
  3. python 常见问题汇总(待续)
  4. [Leedcode][JAVA][第837题][新21点][动态规划][数学]
  5. 腾讯2019秋招笔试真题
  6. 百面机器学习!算法工程师面试宝典!| 码书
  7. mysql查询连续次数_Mysql如何查询连续的时间次数
  8. SSM高校实验室安全培训系统设计与实现.docx
  9. C++-dllmain介绍
  10. 数据不正态分布如何办?
  11. 软件工程大学生职业规划书
  12. 双线虚拟主机服务器,国内双线虚拟主机是什么
  13. 数据库服务器文件路径,服务器数据库的文件路径
  14. 如何查询期刊的中科院分区
  15. java 微信多线程推送_Java实现 微信小程序 + 消息推送
  16. 百度地图 添加 左键菜单 Cannot read property 'remove' of undefined
  17. 联通在线信息科技有限公司社招!
  18. 台式机利用独立显卡连接两个显示器
  19. 地图上如何量方位角_地图投影怎么做到按条件(等角、等面积、等距)投影的?...
  20. DVWA平台漏洞测试与源码分析(一)SQL注入

热门文章

  1. MathJax 支持的 Latex 符号总结(微积分常用符号)
  2. PAT甲级 1030 Travel Plan
  3. 使用 Spire.Pdf的横竖打印设定
  4. uniapp onshow如何获得option参数
  5. 并查集解决朋友圈问题
  6. 通过两张趣图,看工程师的自我反省
  7. Android 天气APP(二十三)增加灾害预警、优化主页面UI
  8. 三极管放大电路的输出电阻
  9. mysql中top命令详解_top命令详解
  10. C语言程序项目计划书,C语言程序的设计课程的设计的计划书.doc