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。

相关推荐
程序员江同学21 小时前
Kotlin 技术月报 | 2025 年 7 月
android·kotlin
_frank2221 天前
kotlin使用mybatis plus lambdaQuery报错
开发语言·kotlin·mybatis
Bryce李小白1 天前
Kotlin实现Retrofit风格的网络请求封装
网络·kotlin·retrofit
ZhuYuxi3331 天前
【Kotlin】const 修饰的编译期常量
android·开发语言·kotlin
Bryce李小白1 天前
Kotlin 实现 MVVM 架构设计总结
android·开发语言·kotlin
Kiri霧1 天前
Kotlin位运算
android·开发语言·kotlin
xjdkxnhcoskxbco1 天前
kotlin基础【3】
android·开发语言·kotlin
小趴菜82271 天前
自定义View和动画学习记录 抓娃娃机View
android·kotlin·动画·自定义view
金銀銅鐵1 天前
Kotlin 中的运算符重载在 class 文件中是如何实现的?(第一部分)
java·kotlin