kotlin StateFlow的两个问题和使用场景探讨

背景说明:

我们日常开发中,经常要在一个独立的界面上做网络请求显示或者toast报错,以及错误信息展示。

LiveData是粘性事件,如果有值(或者有初始值),再注册监听,就会立刻触发。然后就是网络请求,将结果设置到LiveData上,等待回调。

这个流程相信是99%的开发任务。其实使用Flow我认为是杀鸡用牛刀。LiveData我认为在这种场景下,是更好的选择。因为我们99%的场景并非"流"!都是一次请求,或者下拉刷新获取一次结果并展示,而且会屏蔽中间的快速刷新动作,避免过多请求。

好了,既然谈到Flow,自然要用它来替代LiveData。

开发实践模板规范

如下是参考开发规范做的:

首先定义一个状态包裹类,便于后续解析和分类:

kotlin 复制代码
sealed class StatusState<out T> {
    object Loading : StatusState<Nothing>()
    //官方就是data class。注意有坑,后续介绍。我推荐移除data
    data class Success<out T>(val data: T) : StatusState<T>() 
    //官方就是data class。注意有坑,后续介绍。我推荐移除data
    data class Error(val message: String?) : StatusState<Nothing>()
}

第2步,在ViewModel申明;然后调用Api接口,赋值给value:

kotlin 复制代码
	//申明为StateFlow
	private val _userInfo = MutableStateFlow<StatusState<Bean>>(StatusState.Loading)
	val userInfo: StateFlow<StatusState<Bean>> = _userInfo.asStateFlow()
	
	fun requestXXX(xxx) {
    	viewModelScope.launch {
	        try {
	        val data = Api.request(xx)
	        _userInfo.value = StatusState.Success(data)
	    } catch (e: Exception) {
	        _userInfo.value = StatusState.Error(e.message)
	    }
    }
}

第3步,在Fragment/Activity中collect,如下的 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) {}}套路也是官方推荐的:

kotlin 复制代码
	onCreate() {
       lifecycleScope.launch {
         repeatOnLifecycle(Lifecycle.State.STARTED) {
             viewModel.userInfo.collect{
                 //解析为正确
				//...ShowDialog()
				// 解析为error
				//...toast(exMsg)
             }
         }
        }
 	}

坑来了

1. 每次onStart就触发一次

比如跳转到下一页返回,或者Home键再返回。立刻就触发。像我这里一回来toast一下,不太合适吧?

为什么会这样:

  • 理解collect{}函数:

    研究了函数代码,可以理解为一个死循环,等待协程唤醒(谁?当然是我们flow发生数据变化);唤醒一次执行一次你的collect的代码。然后,进入下一次等待。

  • 理解repeatOnLifecycle(Lifecycle.State.STARTED):

    当生命周期离开STARTED即onStop的时候,把collect这个执行死循环给cancel掉了。然后,当STARTED即onStart的时候,立刻触发collect{}动作,死循环又回来了。

这就是repeatOnLifecycle(Life...START) + flow collect的原理。

  • 再看StateFlow的总结特性,"热流"属性,始终持有最新状态值(通过 value 属性)。
    新订阅者立即获取最新值:当收集重新启动时(进入 STARTED),StateFlow 会立即将当前最新值(value)发送给收集器,即使该值之前已发送过。

这就导致了它的触发。但往往我们不需要这种特性。

2. 数据相同不触发

总结图中有说了,自动去重,连续相同值不会触发更新。虽然你的网络请求已经执行,用户手都点麻了,但是就是不触发collect。按道理来讲,相同的网络结果,也就你点了几次,就弹几次,不然疯狂刷新没个反馈,不好吧?

而且明明我每次都是新建的对象呀,为什么也不更新?

kotlin 复制代码
	//网络请求结果设置
	_userInfo.value = StatusState.Error(it.msg)

这又是另外一个坑,原因在于,data class会自行实现equals各个字段比较,相同就不触发collect。因此当我改成不使用data class,那么他每次比较的是2个对象的地址,自然是不一样的。这与LiveData 或者 SharedFlow是有区别的,liveData允许使用重复的对象。

怎么解决

使用SharedFlow。

首先它没有初始值。并且,它不会相同对象或者equals相同就不更新,就可以使用data class。

而且每次重新collect的时候,并无缓存(你不修改MutableSharedFlow申明参数),也就不会触发。

我想SharedFlow才是替代LiveData的真正最佳对象。

总结

回归我前面说的,我们真的需要使用StateFlow吗?

我想我们在99%普通页面上的需求的时候,应该优先需要的是SharedFlow。

相关推荐
zhangphil8 小时前
Android Page3与Flow分页查媒体数据库展示宫格图片列表,Kotlin
android·kotlin
胡致和1 天前
配置变更后,弹窗为什么飞到了最左边?
kotlin
zhangphil1 天前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
小书房1 天前
Kotlin使用体验及理解1
android·开发语言·kotlin
Kapaseker1 天前
我想让同事知道我很懂 Compose 怎么办?
android·kotlin
jinanwuhuaguo2 天前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
jinanwuhuaguo2 天前
OpenClaw协议霸权——从 MCP 标准到意图封建化的政治经济学(第十八篇)
android·人工智能·kotlin·拓扑学·openclaw
zhangphil2 天前
Android sql查媒体数据封装room Dao构造AndroidViewModel,RecyclerView宫格展示,Kotlin
android·kotlin
jinanwuhuaguo2 天前
反熵共同体——OpenClaw的宇宙热力学本体论(第十七篇)
大数据·人工智能·安全·架构·kotlin·openclaw
pengyu2 天前
【Kotlin 协程修仙录 · 筑基境 · 中阶】 | 身份证与通行证:CoroutineContext 的深度解剖
android·kotlin