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