Kotlin Flow常见场景下的使用实例
作者:newki 发布时间:2023-01-30 10:46:25
Kotlin Flow在开发中的常用场景使用
大家了解了 Flow 的创建与接收流程,了解 SharedFlow 创建的几种方式,各个参数的用途,了解了SharedFlow的 "青春版" StateFlow
的创建与接收,已经他们与 LiveData 的异同。
注:这里青春版打上引号,只是调侃而已,并不是说 StateFlow 比 SharedFlow 更加轻量,而是StateFlow使用更加简单,更加的场景化而已,使用起来感觉比较青春版而已。
那么在真实的开发环境中我们是如何使用Flow的呢?这里从几点举例说明一下。
一、网络请求搭载Retrofit
之前在网上看到有人提问,为什么Retrofit不能返回 Flow 这样的对象。使用一个 FlowCallAdapterFactory 那我就可以直接使用Flow来传递了。
不是不行,有第三方的依赖实现了此功能,为什么官方不出,其实官方已经给出了建议。
如果我想使用flow来传递数据,有哪些方式返回Flow的方式呢?这里有几种方案
1.1 LiveDataCallAdapterFactory
Retrofit 增加了 LiveDataCallAdapterFactory,我们可以使用LiveData来包裹对象
interface NewsApi {
@POST("/wanandroid")
fun fetchNewsLiveData(
@FieldMap map:Map<String,String>
):LiveData<ApiResponse<NewsBean>>
}
使用的时候
fetchNewsLiveData().asFlow()
这样不就转成了Flow了吗?如果想转为StateFlow 或者SharedFlow,可以继续shareIn stateIn 之类的方法转换为热流。
1.2 suspend
使用挂起函数直接返回对象,然后使用flow函数创建出Flow对象,也是非常的简单,这也是官方推荐的方式。
interface NewsApi {
@POST("/wanandroid")
suspend fun fetchNews(
@FieldMap map:Map<String,String>
):ApiResponse<NewsBean>
}
使用的时候,直接就创建了一个flow对象
flow {
emit(fetchNews())
}
如果想转为StateFlow 或者SharedFlow,可以继续shareIn stateIn 之类的方法转换为热流。
这种网络数据使用Flow的方式,好处是可以很方便的进行合并,合流,展平等操作,很方便的使用操作符转换成我们想要的数据。
二、协程与Flow的选择与差异
协程与Flow的选择,什么情况下我应该使用协程请求网络,什么情况下我才使用Flow 来操作UI。
其实我们对于实时性不高的数据,我们可以使用 Kotlin 协程处理,而对于实时性较高的数据,我们可以用 Flow 来处理。
例如动态详情顶部是详情数据固定的数据,而底部是列表和点赞评论的数量,这些是动态的数据,那么我们再顶部就可以用协程请求,在底部我们使用Flow处理数据再通知其改变。
@POST("/wanandroid")
suspend fun fetchNews(
@FieldMap map: Map<String, String>
): BaseBean<NewsBean>
suspend fun fetchNewsDetail(): OkResult<NewsBean> {
return extRequestHttp {
DemoRetrofit.apiService.fetchNews(
mapOf("id" to "12232", "key" to "2")
)
}
}
lifecycleScope.launch {
val detail = mViewModel.mRepository.fetchNewsDetail()
detail.checkSuccess {
updateUI(it)
}
}
private fun updateUI(newsBean: NewsBean?) {
// XXX
}
而下面的列表与动态点赞分享数据,我们可以使用 Flow 来操作,当点赞或转发数发生变化时,updateUI() 会被执行,UI根据最新的数据更新。
private val _stateFlow = MutableStateFlow("")
val stateFlow: StateFlow<String> = _searchFlow
fun changeState() {
viewModelScope.launch {
val detail = mRepository.changeState()
detail.checkSuccess {
//进一系列的数据合流
//进行一系列的排序、转换之后设置给Flow
_stateFlow.value = it ?: ""
}
}
}
操作UI的伪代码如下:
private fun changeData() {
mViewModel.changeState()
}
private fun updateUI() {
//更新一些UI
}
override fun startObserve() {
lifecycleScope.launchWhenCreated {
mViewModel.stateFlow.collect {
updateUI()
}
}
}
是不是静态的页面不能用 Flow ,能不能用 LiveData ? 当然可以用了,上面的只是推荐使用,其他的方式当然都可以例如:
fun getNewsDetail(): LiveData<NewsBean?> {
return liveData {
val detail = mRepository.fetchNewsDetail()
if (detail is OkResult.Success) {
emit(detail.data)
} else {
emit(null)
}
}
}
使用的时候:
fun getData(){
mViewModel.getNewsDetail().observe(this) {
updateUI()
}
}
private fun updateUI() {
//更新一些UI
}
三、StateFlow与SharedFlow的选择
什么时候使用StateFlow ,什么时候使用 SharedFlow ,在之前 SharedFlow 的文章中,我们对比过 StateFlow,SharedFlow,LiveData 的区别。
关于 SharedFlow、StateFlow、LiveData的对比,个人的结论是:根据不同的场景 LiveData StateFlow SharedFlow 都有自己特定的使用场景,谁也无法真的完全平替谁。谁也不是谁的超集,都是各有利弊,按需选择即可。这里不过多赘述。
那其实从另一角度,我们区别不同的场景为状态和事件,看此场景是状态驱动还是事件驱动的。
比如我现在点击了按钮,需要弹窗了,然后使用StateFlow来记录状态,然后收集到这个事件弹出弹框了,然后我们关闭弹窗去浏览此页面的其他信息了了,但是当我们旋转手机屏幕之后,我们会发现弹窗又出来了。这就不合理了。
有同学说,这是StateFlow的问题,此情况我们需要使用LiveData,那LiveData就没有问题了吗?
我们测试一下:
@HiltViewModel
class Demo4ViewModel @Inject constructor(
val mRepository: Demo5Repository,
val savedState: SavedStateHandle
) : BaseViewModel() {
val channel = Channel<String>(Channel.CONFLATED)
private val _searchLD = MutableLiveData<String>()
val searchLD: LiveData<String> = _searchLD
private val _searchFlow = MutableStateFlow("")
val searchFlow: StateFlow<String> = _searchFlow
private val _sharedFlow = MutableSharedFlow<String>(replay = 1, onBufferOverflow = BufferOverflow.SUSPEND)
val sharedFlow: SharedFlow<String> = _sharedFlow
fun changeSearch(keyword: String) {
_sharedFlow.tryEmit(keyword)
_searchFlow.value = keyword
_searchLD.value = keyword
channel.trySend(keyword)
}
}
我们测试 LiveData Channel StateFlow SharedFlow(replay=1)
点击按钮发送事件
旋转屏幕查看Log-3个数据
除了Channel,原来你们都会再次触发,别急我们修改SharedFlow(replay =0)
旋转屏幕查看Log-2个数据
SharedFlow就不会再触发了。
到这里,StateFlow 与 SharedFlow 的使用场景就应该很清晰了,状态(State)用 StateFlow ;事件(Event)用 SharedFlow
关于SateFlow SharedFlow LiveData 的对比可以看这里。
来源:https://juejin.cn/post/7127844315925053477
猜你喜欢
- 1、首先说一下用法,BigDecimal中的divide主要就是用来做除法的运算。其中有这么一个方法.public BigDecimal d
- 前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分。它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home
- 本文实例为大家分享了C++ socket实现miniFTP的方法,供大家参考,具体内容如下客户端:服务端:建立连接 &
- 相信很多人在开发过程中经常会遇到需要对一些重要的信息进行加密处理,今天给大家分享我个人总结的一些加密算法:常见的加密方式分为可逆和不可逆两种
- 本文实例为大家分享了Java实现登录和注册的具体代码,供大家参考,具体内容如下登录和注册案例的分析:我们在完成一个需求时,需要面向对象,我们
- 在Java中可以使用HttpServer类来实现Http服务器,该类位于com.sun.net包下(rt.jar)。实现代码如下:主程序类p
- 一、模糊查询的几种实现方式1.concat函数和#{}拼接的方式student_name like concat('%',#
- 前言用户注册功能是每一个系统的入口门面功能,很多人可能会以为很简单,不就是一个简单的CRUD吗?其实不然,要把前后端功能都做出来,页面跳转也
- 在Android原生的TextView的基础上,可收缩/扩展的TextView:PhilExpandableTextView。实现原理:核心
- 概述主要用于Java线程里指定时间或周期运行任务。Timer是线程安全的,但不提供实时性(real-time)保证。构造函数Timer()默
- 简介关于IDEA的介绍,引用自百度百科:IDEA全称 IntelliJ IDEA,是java编程语言开发的集成环境。IntelliJ在业界被
- Java平台的垃圾收集机制显著提高了开发者的效率,但是一个实现糟糕的垃圾收集器可能过多地消耗应用程序的资源。在Java虚拟机性能优化系列的第
- Android中的ListView应该算是布局中几种最常用的组件之一了,使用也十分方便,下面将介绍ListView几种比较常见的优化方法:首
- 1、每帧检查定义一个时间变量 timer,每帧将此时间减去帧间隔时间 Time.deltaTime,如果小于或者等于零,说明定时器到了,执行
- 把spring-boot项目按照平常的web项目一样发布到tomcat容器下一、修改打包形式在pom.xml里设置 <packagin
- MongoDB是介于关系数据库和非关系数据库之间的一种产品,文件的存储格式为BSON(一种JSON的扩展),这里就主要介绍Java通过使用m
- Linux+Docker+SpringBoot+IDEA一键自动化部署的步骤记录从打包到服务器配置上线全流程安装docker详细步骤请戳这里
- 引言最近,因为开发的时候经改动依赖的库,所以,我想对 Gradle 脚本做一个调整,用来动态地将依赖替换为源码。这里以 android-mv
- 前言:学习了SpringBoot分页查询的两种写法,一种是手动实现,另一种是使用框架实现。现在我将具体的实现流程分享一下。首先是手动实现分页
- 前言HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要