注册

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

对于初学者来说使用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>)

其中Recipe实体是每个菜谱


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

模拟四个请求为:


val plannlist = Request.getPlannlist()


val favouritelist= Request.getFavouritelist()


... 以此类推


如果按照要求四个请求返回次序不同,同时要求在列表中按顺序显示,如果实现?


方案一:可以等待四个请求都返回后然后组装数据,刷新列表


可以利用协程的await方法:


val dataList = MutableLiveData<List<Constainer>>()

viewModelScope.launch {
// planner
val plannerDefer = async { Request.getPlannlist() }

// favourite
val 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的流动,同时它是有序的。


后续章节继续讲述flow其他特性,并彻底弃用liveData。


作者:_小猪睡枕头_
链接:https://juejin.cn/post/7054053306158563359
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册