关于android:Android-app中这样用flow更方便加载列表数据

原文地址 https://blog.csdn.net/mjlong1…

背景

        flow简略的能够了解为数据流,它能够生成间断的同类型数据。刚接触到flow的开发者都很纳闷,它的性能如同都有货色能够代替。比方通过foreach遍历Collection或Sequence都能有flow一样的生成数据成果,那为什么还要引入flow呢。大家可能会认为flow实现了观察者模式,这点与collection或sequence的遍历不同。其实LiveData就是依照观察者模式设计的,LiveData配合汇合的遍历就能够达到数据被察看的目标。

        刚接触flow时想了解它的实质目标的确有点吃力,然而通过简略的实际后咱们发现他的劣势体现在与协程的配合上。大家想一想Collection或是sequence的操作反对挂起吗?答案是否定的,它们不反对。然而flow的操作都是挂起函数,用户能够在flow的不同操作中调用其余的挂起函数,并且flow还能够通过flowon来切换flow所运行的协程。

flow 介绍

flow特质:

在协程中与产生一条数据的挂起函数比,flow能够有序生成多条数据。
与生成多条数据的Iterator相比,flow在数据生成的过程中能够调用挂起函数异步生成数据,同时不会阻塞以后线程。
生成的数据序列是同类型的数据。
flow中的三个角色:

数据的生成者=》能够通过挂起函数异步生产一系列数据。
中介者=》能够对生成的数据进行批改。
数据的消费者=》应用生成的数据,个别用户界面展现。

    flow{// 生成1,2,3数据序列
        emit(1)
        emit(2)
        emit(3)
    }.map { 
        value -> value * 2 //批改数据
    }.collect { 
        result-> println(result) //显示转换过的数据
    }

flow加载列表数据
        Android利用加载列表数据是一个比拟广泛的需要,咱们如何应用flow实现列表数据的加载和显示呢?

        首先咱们先剖析下再加载列表数据都须要解决哪些问题:

  1. 加载数据过程中显示loading,数据加载实现暗藏loading。

flow {

    val ret = serverApi.getList(requestId)
    emit(ret)
}.onStart {
    progressBar.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}.collect()

        onStart在数据流开始收集的时候被调用,onCompletion在数据流完结时被调用。这外面数据是通过挂起办法getList生成的繁多数据,所以这个数据流生成一条数据后就完结了。咱们能够发现这里通过数据流的链式解决再配合协程的挂起函数,咱们能够防止异步回调的应用。

2.当加载的数据为空时显示空画面。

flow {

    val ret = serverApi.getList(requestId)
    if (ret.isNotEmpty()) {
        emit(ret)
    }
}.onStart {
    progressBar.visibility = View.VISIBLE
}.onEmpty {
    loadDataRetryButton.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}.collect()

        onEmpty在数据为空时被调用,那什么状况是数据为空呢?其实数据流的数据为空只的是数据流被收集时,数据流没有生成任何数据,在这里就是没有调用emit发射任何数据的时候。咱们能够看到ret.isNotEmpty的判断,只有数据不为空时才进行发射,数据为空时没有发射任何数据,这时onEmpty被调用。

3.获取数据过程中发送异样时,咱们须要显示异样画面。

flow {

    val ret = serverApi.getList(requestId)
    if (ret.isNotEmpty()) {
        emit(ret)
    }
}.onStart {
    progressBar.visibility = View.VISIBLE
}.onEmpty {
    loadDataRetryButton.visibility = View.VISIBLE
}.catch {
    msgTextView.visibility = View.VISIBLE
    msgTextView.text = "发送异样"
    loadDataRetryButton.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}.collect()

        catch在数据流生成过程中产生异样的时候被调用,咱们在catch块中显示错误信息。有一点须要留神,catch块写的地位间接影响了捕捉异样的范畴。在flow的链式调用中,catch块只会捕捉链式调用中它后面的解决产生的异样。

4.显示flow生成的列表数据

flow {

    val ret = serverApi.getList(requestId)
    if (ret.isNotEmpty()) {
        emit(ret)
    }
}.onStart {
    progressBar.visibility = View.VISIBLE
}.onEmpty {
    loadDataRetryButton.visibility = View.VISIBLE
}.onEach {
    adapter.setData(it)
    adapter.notifyDataSetChanged()
}.catch {
    msgTextView.visibility = View.VISIBLE
    msgTextView.text = "发送异样"
    loadDataRetryButton.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}.collect{
    print(it)
}

        onEach在每条数据被发射后会被调用,咱们能够在这里接管并显示数据。当然咱们也能够在collect中显示数据,然而onEach有个劣势,它能够写在catch块后面,这样onEach中产生的异样也能够被catch块捕捉,collect就没有这样的劣势。

5.在网络数据获取失败的状况下应用本地缓存的数据。

flow {

    val ret = serverApi.getList(requestId)
    if (ret.isNotEmpty()) {
        emit(ret)
    }
}.onStart {
    progressBar.visibility = View.VISIBLE
}.onEmpty {
    loadDataRetryButton.visibility = View.VISIBLE
}.catch {
    if (cacheList.isEmpty()) {
        msgTextView.text = "产生异样"
        loadDataRetryButton.visibility = View.VISIBLE
    } else {
        emit(cacheList)
    }
}.onEach {
    cacheList = cacheList
    adapter.setData(it)
    adapter.notifyDataSetChanged()
}.catch{
    msgTextView.text = "onEach异样"
    loadDataRetryButton.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}.collect{
    print(it)
}

        在onEach块中咱们把胜利获取的数据进行保留,而后在catch块中咱们判断是否有缓存数据,如果有缓存数据则向上游发射。这里须要留神的是catch块中调用emit发射的数据只能被链式调用的catch块前面的操作接管到。这里大家可能要问,在onEach中发射的异样咱们如何捕捉?其实在链式操作中,所有的操作都能够应用屡次,所以咱们能够在onEach块后追加一个catch块来捕捉onEach中产生的异样。

6.数据获取和解决的过程中能够不便的切换线程,挂起线程而不是阻塞线程。

var listDataFlow= flow {

    val ret = serverApi.getList(requestId)
    if (ret.isNotEmpty()) {
        emit(ret)
    }
}flowOn(Dispatchers.IO)
.onStart {
    progressBar.visibility = View.VISIBLE
}.onEmpty {
    loadDataRetryButton.visibility = View.VISIBLE
}.catch {
    if (cacheList.isEmpty()) {
        msgTextView.text = "产生异样"
        loadDataRetryButton.visibility = View.VISIBLE
    } else {
        emit(cacheList)
    }
}.onEach {
    cacheList = cacheList
    adapter.setData(it)
    adapter.notifyDataSetChanged()
}.catch{
    msgTextView.text = "onEach异样"
    loadDataRetryButton.visibility = View.VISIBLE
}.onCompletion {
    progressBar.visibility = View.INVISIBLE
}

lifecycleScope.launch { listDataFlow.collect() }

getList办法是耗时办法,通常须要异步线程配合回调函数来解决。flow反对挂起办法调用,所以这里的getList形式被申明成suspend办法,而后通过flowOn办法切换到IO线程执行getList办法。flowOn只影响链式调用中它后面的办法的执行线程,对前面的办法执行线程没有影响。那么前面的办法执行在哪个线程呢?答案是前面的办法执行在收集办法collect被调用的线程。这里启动协程时没有指定线程,所以它执行在Android的主线程中。

总结

        应用flow的形式加载列表数据时有上面几个特点:

flow的链式调用代替了异步回调的形式,代码简洁易懂,防止了异步回调重复嵌套的问题。
应用flowOn办法能够不便灵便地进行线程切换,并且在flow操作中都反对挂起办法,flow能够无缝对接协程。
flow处理过程是申明式的,只有flow被收集的时候这些申明的过程才被执行。申明式的过程还有个益处是咱们能够基于已有的flow申明再追加新的处理过程申明。
        这篇文章以最简略的形式展现了flow加载列表数据的流程,在理论利用中必定要更简单些。这里的flow申明都在fragment中,理论利用中还要进行根本的分层解决。flow的申明属于DataSource层的。在flow向上传递的过程中,咱们能够为底层的flow申明新的解决,比方在repository层追加申明本地缓存解决,在viewmodel层追加申明ui状态更新解决等。实质就是将例子中的解决合成到不同档次上进行追加申明。

        我的公众号曾经开明,公众号会同步公布。
欢送关注我的公众号

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据