Jetpack架构演变:初步使用flow,附加经典案例

在jetpack体系中 livedata的角色纯纯粹粹是个桥接器,DataSource中获取到数据,而后由viewmodel停止逻辑处理,最初被livedata.postValue到view层,唯一的价值是绑定了lifecycle, 只在页面活泼(start)的时候承受数据
官网的一篇介绍能够参考:从 LiveData 迁徙到 Kotlin 数据流 - 掘金
对于初学者来说使用lieveData的益处是足够简略和绝对平安
引入flow次要因为以下几点:

具备更敌对的API,学习成本较低
跟Kotlin协程、LiveData分离更严密,Flow可能转换成LiveData,在ViewModel中间接使用
分离协程的作用域,当协程被勾销时,Flow也会被勾销,防止内存走漏
flow库从属于kotlin, livedata属于Android, 托付Android平台的限度对于将来跨平台开展无利

【flow是个冷数据流】

所谓冷流,即上游无消费行为时,上游不会产生数据,只需上游开始生产,上游才开始产生数据。
而所谓热流,即无论上游是否有消费行为,上游都会本人产生数据。
下边通过一个经典场景粗疏描绘下flow(单纯的flow,而stateFlow会在后续章节中解说)的使用
案例:一个菜谱利用app中,我想在一个页面展现一个列表(recyclerview) ,此列表的每个item是个子列表,子列表顺次为
打算菜谱列表;
珍藏菜谱列表;
依据食材抉择的菜谱列表;
依据食材获取用户偏好的菜谱列表;

四个子列表需要四个接口来获取,组装好起初刷新最初的列表
其中每个列表都有可能是空,是emptylist的话这行就不浮现了,因为四个接口数据量大小不同,所以不会同一时间返回,同时又要保障这四个子列表按请求的次序来展现。
思路:
设计数据结构,最外层的data:

data class ContainerData(val title : String , val list: List)

复制代码
其中Recipe实体是每个菜谱

data class Recipe(val id: String,               val name: String,               val cover: String,               val type: Int,               val ingredients: List? = mutableListOf(),               val minutes: Int,               val pantryItemCount : Int )

复制代码
模仿四个恳求为:

val plannlist = Request.getPlannlist()val favouritelist= Request.getFavouritelist()

... 以此类推
假如依照请求四个恳求返回秩序不同,同时请求在列表中按次序浮现,假如实现?
计划一:能够等待四个恳求都返回后而后组装数据,刷新列表
能够利用协程的await办法:

val dataList = MutableLiveData()viewModelScope.launch {// plannerval plannerDefer = async { Request.getPlannlist() }// favouriteval favouriteDefer = async { Request.getFavouritelist() }val plannerData = plannerDefer.await()val favouriteData = favouriteDefer.await()....省略后两个接口val list = listof(Container("planner" , plannerData),Container("favourite" , favouriteData),...)dataList.postValue(list)}

复制代码
await() 办法是挂起协程,四个接口异步恳求(非次序),等最初一个数据恳求返回后才会执行下边的步骤
而后组装数据利用liveData发送,在view中渲染

viewModel.dataList.observe(viewLifecycleOwner) { mAdapter.submitList(it)}

复制代码
此种形式简略,并且无效处理了按次序排列四个列表的需要,缺点是体验差,假设有一个接口极慢,其余几个就会等待它,用户看着loading不断发愣么。
计划二:接口间不再相互等待,哪个接口先回来就渲染哪个,问题就是如何保障次序?
有的同学会有计划:先定制一个空数据list

val list = listOf(Container("planner", emptylist()),Container("favourite", emptylist()),...)

复制代码
而后先用adapter去渲染list,哪个接口回来就去之前的列表查找交换,而后adapter刷新对应的数据,当然能够,不过会产生一部分逻辑胶水代码,查找遍历的操作。
此时咱们能够借助flow来实现了
1 结构一个planner数据流

val plannerFlow = flow {    val plannList = Request.getPlanlist()emit(ContainerData("Planner", plannList))}.onStart {    emit(ContainerData("", emptylist()))}

复制代码
注意是个val 变量, 不要写成 fun plannerFlow() 办法,不然每次调用开拓新栈的时候新建个flow,并且会不断保存在内存中,直到协程勾销
其中onStart 会在发送正式数据之前发送,作为预加载。
而后咱们就能够结构正式恳求了

viewModelScope.launch {  combine(plannerFlow , favouriteFlow , xxxFlow ,xxxFlow) { planner , favourites , xxx , xxx  ->            mutableListOf(planner , favourites , xxx,xxx)  }.collect {            datalist.postValue(it)     } }

复制代码
combine 的官网正文为

Returns a Flow whose values are generated with transform function by combining the most recently emitted values by each flow.

复制代码
combine操作符能够连接两个不同的Flow , 一旦产生数据就会触发组合后的flow的流动,同时它是有序的。