热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

FlutterStream简介及部分操作符使用

前言对于刚接触Flutter的同学来说,Stream(流)是一个相对比较抽象,也相对比较难以理解的东西。准确的来说Stream并不是Flutter的特性,

前言

    对于刚接触Flutter的同学来说,Stream(流)是一个相对比较抽象,也相对比较难以理解的东西。准确的来说Stream并不是Flutter的特性,而是Dart语言自身所带库。StreamFuture都位于dart:async核心库,是Dart中异步操作的两大高手。所以不仅仅可以用于Flutter,而是可以用于任何Dart语言上的实现。

    在我们刚开始学习Flutter的时候基本都是使用 StatefulWidgetsetState((){})来刷新界面的数据,当我熟练使用流之后就可以基本完全使用StatelessWidget告别 StatefulWidget同样达到数据刷新效果。

Stream分类

流可以分为两类:

  • 单订阅流(Single Subscription),这种流最多只能有一个监听器(listener)

  • 多订阅流(Broadcast),这种流可以有多个监听器监听(listener)

Stream创建方式

  • Stream.fromFuture 接收一个Future对象作为参数

    Stream<String>.fromFuture(getData());Future<String> getData() async{await Future.delayed(Duration(seconds: 5));return "返回一个Future对象";}

  • Stream.fromIterable 接收一个集合对象作为参数

    Stream<String>.fromIterable([&#39;A&#39;,&#39;B&#39;,&#39;C&#39;]);

  • Stream.fromFutures 接收一个Future集合对象作为参数

    Stream<String>.fromFutures([getData()]);

  • Stream.periodic 接收一个 Duration对象作为参数

    Duration interval &#61; Duration(seconds: 1);
    Stream<int> stream &#61; Stream<int>.periodic(interval);

操作方式及使用

使用Stream .periodic 方式创建一个Stream对象

用于创建一个周期发送事件的流&#xff0c;如下

///用于创建一个每隔一秒发送一次无限事件的流,并打印出值
void _stream() async{Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);await for(int i in stream ){print(i); }
}//(data){return data;} 上面的这句是用来回来值&#xff0c;如果省略这句&#xff0c;打印的值都为null

.

  • Stream.take(int count)

    上面创建了一个无限每隔一秒发送一次事件的流&#xff0c;如果我们想指定只发送10个事件则&#xff0c;用take。下面就只会打印出0-9

    void _stream() async{Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10); //指定发送事件个数await for(int i in stream ){print(i);}
    }

  • Stream.takeWhile

    上面这种方式我们是只制定了发送事件的个数&#xff0c;如果我们也不知道发送多少个事件&#xff0c;我们可以从返回的结果上做一个返回值的限制,上面结果也可以用以下方式实现

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);
    // stream &#61; stream.take(10);stream &#61; stream.takeWhile((data) {return data <10;});await for (int i in stream) {print(i);}}

  • Stream.skip(int count)

    skip可以指定跳过前面的几个事件,如下会跳过0和1&#xff0c;输出 2-9;

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);stream &#61; stream.skip(2);await for (int i in stream) {print(i);}}

  • Stream.skipWhile

    可以指定跳过不发送事件的指定条件&#xff0c;如下跳过0-4的输出&#xff0c;输出5-9

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);stream &#61; stream.skipWhile((data) &#61;> data<5);await for (int i in stream) {print(i);}}

  • Stream.toList()

    将流中所有的数据收集存放在List中&#xff0c;并返回 Future对象&#xff0c;listData里面 0-9

    1.这个是一个异步方法&#xff0c;要结果则需要使用await关键字

    2.这个是等待Stream当流结束时&#xff0c;一次返回结果

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);List<int> listData &#61; await stream.toList();for (int i in listData) {print(i);}}

  • Stream. listen()

    这是一种特定的可以用于监听数据流的方式&#xff0c;和 forEach循环的效果一致&#xff0c;但是返回的是StreamSubscription对象&#xff0c;如下也会输出0-9,同时打印出 ”流已完成“

    看一下源码这种方式可以接收

    StreamSubscription listen(void onData(T event),{Function onError, void onDone(), bool cancelOnError});

    1.onData是接收到数据的处理,必须要实现的方法

    2.onError流发生错误时候的处理

    3.onDone流完成时候调取

    4.cancelOnError发生错误的时候是否立马终止

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);stream.listen((data) {print(data);}, onError: (error) {print("流发生错误");}, onDone: () {print("流已完成");}, cancelOnError: false);}

  • Stream. forEach()

    这中操作和listen()的方式基本差不多&#xff0c;也是一种监听流的方式&#xff0c;这只是监听了onData,下面代码也会输出0-9

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);stream.forEach((data) {print(data);});}

  • Stream .length

    用于获取等待流中所有事件发射完成之后统计事件的总数量,下面代码会输出 10

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.take(10);var allEvents &#61; await stream.length;print(allEvents);}

  • Stream.where

    在流中添加筛选条件&#xff0c;过滤掉一些不想要的数据&#xff0c;满足条件返回true,不满足条件返回false,如下我们筛选出流中大于5小于10的数据

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream<int>.periodic(interval, (data) &#61;> data);stream &#61; stream.where((data)&#61;>data>5);stream &#61; stream.where((data)&#61;> data<10);await for(int i in stream){print(i);}}

  • stream.map

    对流中的数据进行一些变换&#xff0c;以下是我对Stream的每个数据都加1

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream<int> stream &#61; Stream<int>.periodic(interval, (data) &#61;> data);stream &#61; stream.map((data) &#61;> data &#43; 1);await for (int i in stream) {print(i);}}

  • Stream.expand

    对流中的数据进行一个扩展&#xff0c;如下&#xff0c;会输出1,1,2,2,3,3….

    void _stream() async {Duration interval &#61; Duration(seconds: 1);Stream stream &#61; Stream.periodic(interval, (data) &#61;> data);stream &#61; stream.expand((data)&#61;>[data,data]);stream.listen((data)&#61;>print(data),onError:(error)&#61;> print("发生错误") );}

  • Stream.transform

    如果我们在在流流转的过程中需要进行一些转换和控制我们则需要使用到transform,接收一个

    StreamTransformer&#xff0c;S表示转换之前的类型&#xff0c;T表示转换后的输入类型&#xff0c;如下代码我们会接收到三组数字模拟输入了三次密码&#xff0c;并判断真确的密码&#xff0c;同时输出密码正确和密码错误&#xff1a;

    void _stream() async {var stream &#61; Stream<int>.fromIterable([123456,234567,678901]);var st &#61; StreamTransformer<int, String>.fromHandlers(handleData: (int data, sink) {if (data &#61;&#61; 678901) {sink.add("密码输入正确,正在开锁。。。");} else {sink.add("密码输入错误...");}});stream.transform(st).listen((String data) &#61;> print(data),onError: (error) &#61;> print("发生错误"));}

    输入如下结果

    I/flutter (18980): 密码输入错误...
    I/flutter (18980): 密码输入错误...
    I/flutter (18980): 密码输入正确,正在开锁。。。

StreamController使用

​ 介绍完了Stream的基本概念和基本用法&#xff0c;上面直接创建流的方式&#xff0c;对我们本身开发来说&#xff0c;用途不是很大&#xff0c;我们在实际的开发过程中&#xff0c;基本都是使用的StreamContoller来创建流。通过源码我们可以知道Stream的几种构造方法&#xff0c;最终都是通过StreamController进行了包装。

创建StreamController对象及使用

  • 构建单订阅的Streamcontroller

//StreamController里面会创建一个Stream&#xff0c;我们实际操控的Stream
StreamController<String> streamController &#61; StreamController();
streamController.stream.listen((data)&#61;> print(data));
streamController.sink.add("aaa");
streamController.add("bbb");
streamController.add("ccc");
streamController.close();//上面代码我们会输出 aaa,bbb,ccc

注意&#xff1a;如果我们给上面的代码再加一个listen会报如下异常,所以单订阅流&#xff0c;只能有一个listen。一般情况下我们多数都是使用的单订阅流&#xff0c;我们也可以将单订阅流转成多订阅流。

  • 构建多监听器的StreamController有两种方式

    1.直接创建多订阅Stream

    StreamController<String> streamController &#61; StreamController.broadcast();streamController.stream.listen((data){print(data);},onError: (error){print(error.toString());});streamController.stream.listen((data) &#61;> print(data));streamController.add("bbb");//上面代码回输出 bbb,bbb

    2.将单订阅流转成多订阅流

    StreamController streamController &#61; StreamController();Stream stream &#61;streamController.stream.asBroadcastStream();stream.listen((data) &#61;> print(data));stream.listen((data) &#61;> print(data));streamController.sink.add("aaa");streamController.close();//上面代码会输出 aaa,aaa

注意&#xff1a;在流用完了之后记得关闭&#xff0c;调用streamController.close()

StreamBuilder使用

​    前面我把Stream的常用方式做了简单的介绍和演示&#xff0c;我们怎么结合Flutter使用呢&#xff1f;在Flutter里面提供了一个WidgetStreamBuilderStreamBuilder其实是个StatefulWidget它一直记录着流中最新的数据&#xff0c;当数据流发生变化时&#xff0c;会自动调用builder方法进行重建。

  • StreamBuilder的源码如下&#xff0c;需要接受一个流&#xff0c;我们可以传入一个StreamControllerStream

    const StreamBuilder({Key key,this.initialData,Stream stream,&#64;required this.builder,}) : assert(builder !&#61; null),super(key: key, stream: stream);

  • 使用StreamController 结合 StreamBuider对官方的计数器进行改进&#xff0c;取代setState刷新页面&#xff0c;代码如下

    class MyHomePage extends StatefulWidget {&#64;override_MyHomePageState createState() &#61;> _MyHomePageState();
    }class _MyHomePageState extends State<MyHomePage> {int _count &#61; 0;final StreamController<int> _streamController &#61; StreamController();&#64;overrideWidget build(BuildContext context) {return Scaffold(body: Container(child: Center(child: StreamBuilder<int>(stream: _streamController.stream,builder: (BuildContext context, AsyncSnapshot snapshot) {return snapshot.data &#61;&#61; null? Text("0"): Text("${snapshot.data}");}),),),floatingActionButton: FloatingActionButton(child: const Icon(Icons.add),onPressed: () {_streamController.sink.add(&#43;&#43;_count);}),);}&#64;overridevoid dispose() {_streamController.close();super.dispose();}
    }

源码相关

    从StreamController源码我们可以看出来里面创建的得过程 默认创建一个_SyncStreamController,具体可以去读取下源码&#xff0c;看看StreamController是怎么创建StreamStreamSink

factory StreamController({void onListen(),void onPause(),void onResume(),onCancel(),bool sync: false}) {return sync? new _SyncStreamController(onListen, onPause, onResume, onCancel): new _AsyncStreamController(onListen, onPause, onResume, onCancel);}

    注意&#xff1a;上面我既使用了streamController.sink.add("aaa");添加数据&#xff0c;也使用了streamController.add("bbb");方式添加数据。实际效果是一样的&#xff0c;查看源码可知 sink如下,实际上sink是对StreamController的一种包装&#xff0c;最终都是调取的StreamController.add方法&#xff1b;

class _StreamSinkWrapper<T> implements StreamSink<T> {final StreamController _target;_StreamSinkWrapper(this._target);void add(T data) {_target.add(data);}void addError(Object error, [StackTrace stackTrace]) {_target.addError(error, stackTrace);}Future close() &#61;> _target.close();Future addStream(Stream source) &#61;> _target.addStream(source);Future get done &#61;> _target.done;

总结

    以上是我在开发过程中&#xff0c;对FlutterStream使用的一些简单的认识和常用方法的总结,StreamBuilderStreamController结合使用&#xff0c;实现局部刷新效果&#xff08;比喻一个页面有多个接口&#xff0c;各自应该刷新自己控制的UI的部分而不应该整个页面刷新&#xff0c;时候我们可以使用StreamControllerStreamBuilder达到这种效果&#xff09;如有不到之处或者有偏差的地方&#xff0c;望指正~

转:https://juejin.im/post/5d3a91d1e51d457778117479



推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
  • 本文介绍了在实现了System.Collections.Generic.IDictionary接口的泛型字典类中如何使用foreach循环来枚举字典中的键值对。同时还讨论了非泛型字典类和泛型字典类在foreach循环中使用的不同类型,以及使用KeyValuePair类型在foreach循环中枚举泛型字典类的优势。阅读本文可以帮助您更好地理解泛型字典类的使用和性能优化。 ... [详细]
  • 本文整理了Java中org.gwtbootstrap3.client.ui.Icon.addDomHandler()方法的一些代码示例,展示了Icon.ad ... [详细]
  • Python中的PyInputPlus模块原文:https ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
author-avatar
手机用户2502923495
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有