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

17次阅读

共计 4673 个字符,预计需要花费 12 分钟才能阅读完成。

原文地址 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 状态更新解决等。实质就是将例子中的解决合成到不同档次上进行追加申明。

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

正文完
 0