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

news/2024/10/22 8:09:31/

在这里插入图片描述

翻译自:

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

前言

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

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

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

示例

我们有一个简单的 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 的数值即可。

img

上面的运行结果相当出乎意料。你可能会争辩道: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 里新的输出。

img

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

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

总结

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

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

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


http://www.ppmy.cn/news/10444.html

相关文章

【Kotlin】空安全 ④ ( 手动空安全管理 | 空合并操作符 ?: | 空合并操作符与 let 函数结合使用 )

【Kotlin】空安全 ④ ( 手动空安全管理 | 空合并操作符 ?: | 空合并操作符与 let 函数结合使用 )

文章目录一、空合并操作符 ?:二、空合并操作符与 let 函数结合使用一、空合并操作符 ?: 空合并操作符 ?: 用法 : 表达式 A ?: 表达式 B如果 表达式 A 的值 不为 null , 则 整个表达式的值 就是 表达式 A 的值 ; 如果 表达式 A 的值 为 null , 则 整个表达式的值 就是 表达…
阅读更多...
华为OD机试 - 微服务的集成测试

华为OD机试 - 微服务的集成测试

题目描述 现在有n个容器服务,服务的启动可能有一定的依赖性(有些服务启动没有依赖),其次服务自身启动加载会消耗一些时间。 给你一个nxn的二维矩阵useTime,其中useTime[i][i]=10表示服务i自身启动加载需要消耗10s, useTime[i][j] = 1表示服务i启动依赖服务j启动完成,us…
阅读更多...
剑指 Offer 56 - I. 数组中数字出现的次数

剑指 Offer 56 - I. 数组中数字出现的次数

题目要求时间复杂度是O(n)&#xff0c;空间复杂度是O(1)。 异或可以将两个只出现一次的数筛选出来&#xff0c;之后再把这两个数分到两组中&#xff0c;并且将相同的数分到同一组&#xff0c;两组分别异或就能求出两个数。 异或规则是不相同为1&#xff0c;相同为0 所以利用a^…
阅读更多...
vitepress(三):自动生成目录

vitepress(三):自动生成目录

上一节我们将自己的网站发布到了git pages上&#xff0c;但是现在我们需要每次更新一篇文章就重写一次目录&#xff0c;操作上十分的繁琐和不方便&#xff0c;所以我们需要一个方法去自动生成我们的侧边栏结构&#xff0c;方便我们每次只需要更新我们的项目即可。这里我们要知道…
阅读更多...
机器学习(4)——周志华

机器学习(4)——周志华

归纳偏好 若非必要&#xff0c;勿增实体 若多个算法都能解决同一个问题&#xff0c;则选取最简单的算法 当前什么样的偏好与问题更匹配&#xff0c;必须对问题有一个清楚的认识
阅读更多...
WSL2下安装MESA / OSMESA驱动

WSL2下安装MESA / OSMESA驱动

找不到的命令可以在这里找https://command-not-found.com/ 写在最前面 如果你和我一样是在用pyrender的时候遇到了以下问题&#xff0c;并且在试了以下方法代码仍然报莫名其妙错误的时候&#xff0c;请通过pip 把pyrender pyopengl 都升到最新版&#xff0c;可以无视pyrender…
阅读更多...
【AcWing周赛】AcWing第85场周赛

【AcWing周赛】AcWing第85场周赛

目录 <一>Acwing 4791. 死或生 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 <二>Acwing 4792. 最大价值 一、题目 1、原题链接 2、题目描述 二、解题报告&#xff1a; 1、思路分析 2、时间复杂度 3、代码…
阅读更多...
【华为机试真题详解】获得完美走位【2022 Q4 | 100分】

【华为机试真题详解】获得完美走位【2022 Q4 | 100分】

文章目录 前言题目描述输入描述输出描述示例 1示例 2题目解析参考代码优化前言 《华为机试真题详解 Python实现》专栏含牛客网华为专栏、华为面经试题、华为OD机试真题。 如果您在准备华为的面试,期间有想了解的可以私信我,我会尽可能帮您解答,也可以给您一些建议! 本文解…
阅读更多...
最新文章

玻璃钢生产厂家濮阳玻璃钢卡通座椅雕塑淄博玻璃钢海豚雕塑定制玻璃钢雪山雕塑代理商拉萨玻璃钢雕塑浮雕厂酒泉玻璃钢雕塑喷漆云南公园玻璃钢雕塑安装玻璃钢雕塑制作时间艺术商场美陈售价茂名玻璃钢人物雕塑代理商德阳玻璃钢雕塑厂家南宁大型玻璃钢雕塑销售厂家河南开业商场美陈多少钱玻璃钢骆驼雕塑制造厂家蒙自市玻璃钢雕塑设计怎么样吉安定制玻璃钢人物雕塑厂家商场中空美陈简单风格大丰玻璃钢花盆花器浙江艺术商场美陈有哪些玻璃钢浮雕房地产雕塑湖南玻璃钢浮雕市政广场雕塑祥符玻璃钢雕塑费用四川通道商场美陈批发价上海特色商场美陈采购奇战马玻璃钢军人雕塑云南特色玻璃钢雕塑订做价格广东户外玻璃钢雕塑市场玻璃钢瓜果雕塑制造厂家嘉兴大象玻璃钢雕塑美陈ip 商场临沧市玻璃钢雕塑设计厂家香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化