携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
Flow的系列文章:
SharedFlow StateFlow LiveData的常用场景化使用
之前的文章单独介绍过SharedFlow StateFlow 。得出的结论它们都不能全面的代替LiveData。在一些特定的场景下还是得使用LiveData。
说一千道一万,我们在开发中哪些时候需要使用使用哪一个对象呢?这里说一下开发中常见的一些场景使用。
一、ViewModel->Activity 发送数据刷新UI
先说SharedFlow,虽然它可以通过构造参数的设置,可以实现StateFlow的功能,但是最关键的是它不能直接getValue,也就是说它可以很方便的发送通知,不能随时获取到值。所以不推荐用于ViewModel到Activity的刷新。
而StateFlow因为内部就是提供的value的暴露。
public interface StateFlow<out T> : SharedFlow<T> {
public val value: T
}
所以我们通常用它来代替LiveData,在ViewModewl中通知Activity获取数据刷新UI。
我们通常这么写LiveData,和 StateFlow。 便于读写分离(MutableStateFlow是可读可写,StateFlow是仅可读),当然如果直接使用 MutableXX 这样可读可写的也能实现的。
@HiltViewModel
class Demo4ViewModel @Inject constructor(
val savedState: SavedStateHandle
) : BaseViewModel() {
private val _searchLD = MutableLiveData<String>()
val searchLD: LiveData<String> = _searchLD
private val _searchFlow = MutableStateFlow("")
val searchFlow: StateFlow<String> = _searchFlow
fun changeSearch(keyword: String) {
_searchFlow.value = keyword
_searchLD.value = keyword
}
}
我们都知道LiveData的监听是和Activity的生命周期绑定的,比如页面再后台就不会触发,而页面到前台才会触发。
StateFlow可不会这么和生命周期绑定,不过我们可以通过添加一个函数让StateFlow可以和生命周期绑定实现和LiveData一样的效果。
fun testValue(){
CommUtils.getHandler().postDelayed({
mViewModel.changeSearch("1234")
},2000)
}
override fun onStart() {
super.onStart()
YYLogUtils.w("页面开始onStart了")
}
override fun startObserve() {
lifecycleScope.launch {
mViewModel.searchFlow
.flowWithLifecycle(this@Demo4Activity.lifecycle) //相当于自动帮我们设置 lifecycle.repeatOnLifecycle
.collect {
YYLogUtils.w("search-state-value $it")
}
}
mViewModel.searchLD.observe(this) {
YYLogUtils.w("search-livedata-value $it")
}
}
我们通过添加一个函数flowWithLifecycle,就可以让Flow拥有生命周期。达到LiveData同样的效果。
我们点击按钮发送Value,由于是延时2秒发送,我们点击Home按钮进入后台,不会触发监听,我们点击回到应用,就会打印StateFlow和LiveData的监听。
如果我就想当前Actiity不可见的情况下也能触发监听,那要怎么搞,也能做,我们StateFlow默认就不绑定生命周期的,我们把绑定生命周期的代码去掉即可,LiveData的observe默认就需要传入一个参数绑定生命周期,我们替换为observeForever方法监听即不绑定生命周期,那么Activity不可见的情况下也能触发监听了。
fun testValue(){
CommUtils.getHandler().postDelayed({
mViewModel.changeSearch("1234")
},2000)
}
override fun onStart() {
super.onStart()
YYLogUtils.w("页面开始onStart了")
}
override fun startObserve() {
lifecycleScope.launch {
mViewModel.searchFlow.collect {
YYLogUtils.w("search-state-value $it")
}
}
mViewModel.searchLD.observeForever {
YYLogUtils.w("search-livedata-value $it")
}
}
我们点击按钮发送Value,由于是延时2秒发送,我们点击Home按钮进入后台,2秒之后就会打印StateFlow和LiveData的监听。
二、哪些情况下不能StateFlow不能平替LiveData
可以看到上面的操作我们都可以通过添加函数或者替换方法的方式可以让StateFlow与LiveData的功能都相同,完全可以平替啊,那么哪些情况下不能平替呢?这里单独讲一讲。
由于StateFlow默认数据防抖,也就是一样的值StateFlow是不会发送的,那么在一些特定的场景下,比如点击登录故意输入错误密码。
@HiltViewModel
class Demo4ViewModel @Inject constructor(
val savedState: SavedStateHandle
) : BaseViewModel() {
private val _searchFlow = MutableStateFlow("")
val searchFlow: StateFlow<String> = _searchFlow
fun login(keyword: String) {
viewModelScope.launch {
// xxx 调用登录接口
val errorMsg = "密码错误"
_searchFlow.value = errorMsg
}
}
}
我们点击登录按钮,调用接口得到错误信息“密码错误”。通过StateFlow发送给Activity。Activity就会弹窗展示错误信息。那么第二次再点击登录按钮,StateFlow就会判断你的值还是“密码错误”,它就不给你发送了。这样就导致用户点击按钮没反应?还以为App挂了呢,所以此时我们就需要使用LiveData。
StateFlow的数据防抖是双刃剑,所以我们需要看使用的场景是否需要此功能。
另一点是如果大家使用MVVM框架,那么DataBinding的使用默认是支持LiveData的。而使用StateFlow有些版本可行,有些版本不行,总的来说还是有坑。导致编译不能通过。
所以说如果使用DataBinding的情况下,推荐使用LiveData。
除了这两种情况,其他情况下使用StateFlow平替LiveData是没什么问题的。
三、哪些场景下才使用SharedFlow
SharedFlow这么没有牌面的吗?你们都比完了都不用我,那我干嘛?
其实SharedFlow相比StateFlow更加灵活,由于它可以处理粘性和背压的问题,一个很常用的场景就是是用于事件的传递。就是类似EventBus那种消息总线。可以很方便很简单的实现一个基于SharedFlow的FlowBus。
另一种情况下就是用于管理页面的事件处理,比如根据值的状态弹出弹窗,吐司。由于SharedFlow可以自由的控制是否需要粘性,如果默认没有粘性的情况下,我们可以在页面销毁重建,或旋转屏幕的时候,保证不会触发到页面状态,如弹窗,吐司的触发。因为没有粘性内部没有值的保存,所以不会触发到页面状态。
这两种场景我已经在SharedFlow的单独介绍文章中有代码与演示,有兴趣的可以看看这里。
总结
我的观点就是如果你使用的MVVM(带DataBinding)的架构(不管是Java语言开发还是Kotlin语言开发),那 SharedFlow StateFlow LiveData 三者各自有各自的使用场景。
如果你使用的Compose的架构项目或者不带DataBinding的MVP架构并且使用的是Kotlin语言
开发,那 StateFlow 完全可以取代 LiveData。
具体情况具体分析,不能脱离使用场景直接说 SharedFlow 取代 LiveData ,在平常开发中可能用到 StateFlow 和 SharedFlow 的场景应该都覆盖到了,所以我们了解三者的优缺点和异同点之后,不同的场景使用不同的对象实现具体的功能。自己掌控即可。
当然了这也是我自己个人的浅见,大家可以有自己的思考与看法,如果觉得有不同的想法,或者更多的一些使用场景,都可以评论区交流。
好了,如果感觉本文对你有一点点点的启发,还望你能点赞
支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。