ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

kotlin flow如何结合Android使用场景

2021-09-20 23:01:47  阅读:720  来源: 互联网

标签:kotlin flow visibility VISIBLE ret Android 数据 View


目录

背景

flow 介绍

flow加载列表数据

总结


背景

        flow简单的可以理解为数据流,它可以生成连续的同类型数据。刚接触到flow的开发者都很疑惑,它的功能好像都有东西可以替代。比如通过foreach遍历Collection或Sequence都能有flow一样的生成数据效果,那为什么还要引入flow呢。大家可能会认为flow实现了观察者模式,这点与collection或sequence的遍历不同。其实LiveData就是按照观察者模式设计的,LiveData配合集合的遍历就可以达到数据被观察的目的。

        刚接触flow时想理解它的本质目的确实有点费劲,但是经过简单的实践后我们发现他的优势表现在与协程的配合上。大家想一想Collection或是sequence的操作支持挂起吗?答案是否定的,它们不支持。但是flow的操作都是挂起函数,用户可以在flow的不同操作中调用其他的挂起函数,并且flow还可以通过flowon来切换flow所运行的协程。

flow 介绍

flow特质:

  1. 在协程中与产生一条数据的挂起函数比,flow可以有序生成多条数据。
  2. 与生成多条数据的Iterator相比,flow在数据生成的过程中可以调用挂起函数异步生成数据,同时不会阻塞当前线程。
  3. 生成的数据序列是同类型的数据。

flow中的三个角色:

  1. 数据的生成者=》可以通过挂起函数异步生产一系列数据。
  2. 中介者=》可以对生成的数据进行修改。
  3. 数据的消费者=》使用生成的数据,一般用户界面展示。
        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的方式加载列表数据时有下面几个特点:

  1. flow的链式调用替代了异步回调的方式,代码简洁易懂,避免了异步回调反复嵌套的问题。
  2. 使用flowOn方法可以方便灵活地进行线程切换,并且在flow操作中都支持挂起方法,flow可以无缝对接协程。
  3. flow处理过程是声明式的,只有flow被收集的时候这些声明的过程才被执行。声明式的过程还有个好处是我们可以基于已有的flow声明再追加新的处理过程声明。

        这篇文章以最简单的方式展示了flow加载列表数据的流程,在实际应用中肯定要更复杂些。这里的flow声明都在fragment中,实际应用中还要进行基本的分层处理。flow的声明属于DataSource层的。在flow向上传递的过程中,我们可以为底层的flow声明新的处理,比如在repository层追加声明本地缓存处理,在viewmodel层追加声明ui状态更新处理等。本质就是将例子中的处理分解到不同层次上进行追加声明。

        我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号

标签:kotlin,flow,visibility,VISIBLE,ret,Android,数据,View
来源: https://blog.csdn.net/mjlong123123/article/details/120394630

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有