如何在Dart&Flutter中使用Stream

更新日期: 2021-01-05阅读: 1.5k标签: Flutter

1 什么是Stream?

Stream是Dart用来处理异步的api,和同样用来处理异步的Future不同的是,Stream可以异步的返回多个结果,而Future只能返回一个


2 如何创建Stream?

1.1使用Stream的构造方法

Stream periodicStream = Stream.periodic(Duration(seconds: 2), (num) {
  return num;
});

periodic构造方法主要有两个参数,第一个参数类型为Duration(时间间隔),第二个参数类型为Function,Function每隔一个Duration(时间间隔)会被调用一次,参数num为事件调用的次数,从0开始依次递增。

翻阅源码 Stream.periodic是使用Timer.periodic加_SyncStreamController实现的

1.2将方法的返回值声明为Stream

Stream<String> timedCounter(Duration interval, [int maxCount]) async* {
  int i = 0;
  while (true) {
   //延迟interval(时间间隔)执行一次
    await Future.delayed(interval);
    //返回i  i++
    yield "stream返回${i++}";
    if (i == maxCount) break;
  }
}

看到这里你可能会有一些疑问什么是async*和yield?

yield为一个用async *修饰返回值为Stream的函数返回一个值,它就像return,不过他不会结束函数

Stream asynchronousNaturalsTo(n) async* {
  int k = 0;
  while (k < n) yield k++;
}

这里涉及到了Dart的生成器函数概念,在这里你只需要简单理解yield的作用就可以了

1.3使用StreamController

  var _controller = StreamController<int>();

  var _count = 1;

  createStream() {
  //函数每隔一秒调用一次
    Timer.periodic(Duration(seconds: 1), (t) {
      _controller.sink.add(_count);
      _count++;
    });
  }

我们主要使用_controller的两个属性,使用_controller.Stream获取流,使用_controller.sink.add向流中添加数据,上面的例子使用定时器,每隔一秒向流中添加数据_count。


3 Stream的常用方法

接下来介绍一下Stream的常用方法

PS:以下Stream常用方法的展示都是用下面代码创建的流

Stream periodicStream = Stream.periodic(Duration(seconds: 1), (num) {
  return num;
});

3.1 listen

listen作为使用Stream最重要的方法,主要用于监听流的数据变化,每当流的数据变化时,listen中的方法都会被调用。

    periodicStream.listen((event) {
      print(event);
    });

listen方法默认参数为Function,参数中的event为上面示例中返回的num,每当流返回新的数据时,listen方法都会被调用。

控制台输出如下

0
1
2
3
4

打印流返回的数据

listen的onError参数当流出现错误时调用。

listen的onDone参数当流关闭时调用。

还有一个cancelOnError属性,默认情况下为true,可以将其设置为false以使订阅在发生错误后也能继续进行。

3.2 map

Stream.periodic(Duration(seconds: 1), (num) {
    return num;
  }).map((num) => num * 2)

使用map将流返回的数据进行转换

控制台输出如下

0
2
4
6

3.3 asBroadcastStream()&broadcast

通过Stream的asBroadcastStream()或StreamController的broadcast将单订阅的流转换为多订阅流

什么是单订阅流和多订阅流?

3.3.1 单订阅流

单订阅流顾名思义,此流只能有一个订阅者,也就是单订阅流的listen方法只能被调用一次,当第二次调用单订阅流的listen时会报错,值得一提的是,当我们创建流时,默认创建的就是单订阅流。

3.3.2 多订阅流

顾名思义,此流可以有多个订阅者,也就是多订阅流的listen方法可以被多次调用,通过Stream的asBroadcastStream()或StreamController的broadcast将单订阅流转换为多订阅流。

创建多订阅流
Stream broadcastStream = Stream.periodic(Duration(seconds: 5), (num) {
  return num;
}).asBroadcastStream();
var _controller = StreamController<int>.broadcast()

3.3.3 单订阅流与多订阅流的区别

第一个区别

第一个区别就是上面提到的订阅者数量的区别

第二个区别

我们重点要谈论一下两种流的第二个区别

第二个区别就是单订阅流会持有自己的数据,当订阅者出现时将自身持有的数据全部返回给订阅者,而多订阅流不会持有任何数据,如果多订阅流没有订阅者,多订阅流会把数据丢弃。

下面我们用两端代码来展示两种流处理数据上的差别

单订阅流代码展示

创建流

  var _controller = StreamController<int>.broadcast();
  var _count = 1;

  createStream() {
    Timer.periodic(Duration(seconds: 1), (t) {
      _controller.sink.add(_count);
      _count++;
    });
  }

订阅流

createStream();
Future.delayed(Duration(seconds: 5), () {
  _controller.stream.listen((event) {
    print("单订阅流$event");
    });
});

控制台输出如下


可以看到,单订阅流即使前五秒我们没有订阅,但单订阅流还是在持有数据,当订阅者出现时将持有的所有数据发送给订阅者。

多订阅流代码展示

创建流

  var _controller = StreamController<int>.broadcast();
  var _count = 1;

  createStream() {
    Timer.periodic(Duration(seconds: 1), (t) {
      _controller.sink.add(_count);
      _count++;
    });
  }

订阅流

    createStream();
    Future.delayed(Duration(seconds: 5), () {
      _controller.stream.listen((event) {
        print("多订阅流$event");
      });
    });
    Future.delayed(Duration(seconds: 10), () {
      _controller.stream.listen((event) {
        print("多订阅流二$event");
      });
    });

控制台输出


可以看到多订阅流产生的前五条数据都被丢弃了,只有订阅者出现后生成的数据被发送给了订阅者。

代码看完想必你已经理解了单订阅流与多订阅流的第二种区别,我制作了两种流程图帮助你理解


3.4 其他方法

处理 Stream 的方法

下面这些 Stream 类中的方法可以对 Stream 进行处理并返回结果:

Future<T> get first;
Future<bool> get isEmpty;
Future<T> get last;
Future<int> get length;
Future<T> get single;
Future<bool> any(bool Function(T element) test);
Future<bool> contains(Object needle);
Future<E> drain<E>([E futureValue]);
Future<T> elementAt(int index);
Future<bool> every(bool Function(T element) test);
Future<T> firstWhere(bool Function(T element) test, {T Function() orElse});
Future<S> fold<S>(S initialValue, S Function(S previous, T element) combine);
Future forEach(void Function(T element) action);
Future<String> join([String separator = ""]);
Future<T> lastWhere(bool Function(T element) test, {T Function() orElse});
Future pipe(StreamConsumer<T> streamConsumer);
Future<T> reduce(T Function(T previous, T element) combine);
Future<T> singleWhere(bool Function(T element) test, {T Function() orElse});
Future<List<T>> toList();
Future<Set<T>> toSet();


4 管理流订阅

我们可以使用StreamSubscription对象来对流的订阅进行管理,listen方法的返回值就是StreamSubscription对象

  StreamSubscription subscription =
    Stream.periodic(Duration(seconds: 1), (num) {
    return num;
  }).listen((num) {
    print(num);
  });

4.1 暂停订阅

subscription.pause();

4.2 恢复订阅

subscription.resume();

4.3 取消订阅

subscription2.cancel();

当不需要监听流时记得调用这个方法,否则会造成内存泄漏

4.4 操作流订阅的例子

以下示例用来展示如何操作流订阅

创建流

  static var _controller = StreamController<int>();
  var _count = 1;
  createStream() {
    Timer.periodic(Duration(seconds: 1), (t) {
      _controller.sink.add(_count);
      _count++;
    });
  }

创建监听及监听管理对象

  StreamSubscription subscription2 = _controller.stream.listen((event) {
    print("单订阅流$event");
  });

操作流订阅的方法

createStream();
Future.delayed(Duration(seconds: 3), () {
  print("暂停");
  subscription2.pause();
});
Future.delayed(Duration(seconds: 5), () {
  print("继续");
  subscription2.resume();
});
Future.delayed(Duration(seconds: 7), () {
  print("取消");
  subscription2.cancel();
});


5 在Flutter中使用StreamBuilder组件

5.1 StreamBuilder组件介绍

StreamBuilder组件主要有两个参数

第一个参数stream,要订阅的流

第二个参数builder,widget构建函数

可以使用builder函数的snapshot.connectionState属性根据流的不同状态返回不同的组件

每当StreamBuilder监听的stream有数据变化时,builder函数就会被调用,组件重新构建。

5.2示例代码

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_demo/util/util.dart';

/// Copyright (C), 2020-2020, flutter_demo
/// FileName: streamBuilder_demo
/// Author: Jack
/// Date: 2020/12/27
/// Description:
class StreamBuilderDemo extends StatelessWidget {
  //创建流
  Stream<int> _stream() {
    Duration interval = Duration(seconds: 1);
    Stream<int> stream = Stream<int>.periodic(interval, (num) {
      return num;
    });
    stream = stream.take(59);
    return stream;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stream Demo'),
      ),
      body: Center(
        child: StreamBuilder(
          stream: _stream(),
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return Text(
                '1 Minute Completed',
                style: TextStyle(
                  fontSize: 30.0,
                ),
              );
            } else if (snapshot.connectionState == ConnectionState.waiting) {
              return Text(
                'Waiting For Stream',
                style: TextStyle(
                  fontSize: 30.0,
                ),
              );
            }
            return Text(
              '00:${snapshot.data.toString().padLeft(2, '0')}',
              style: TextStyle(
                fontSize: 30.0,
              ),
            );
          },
        ),
      ),
    );
  }
}


6 完整示例

上文所有的代码示例都在作者的GiuHub上,https://github.com/jack0-0wu/flutter_demo,里面还包含了一些常用flutter功能的展示。


链接: https://fly63.com/article/detial/10046

移动跨平台框架Flutter介绍和学习线路

Flutter是一款移动应用程序SDK,一份代码可以同时生成iOS和Android两个高性能、高保真的应用程序。Flutter目标是使开发人员能够交付在不同平台上都感觉自然流畅的高性能应用程序。我们兼容滚动行为、排版、图标等方面的差异。

初识flutter

关注flutter已经好久,因为没有发正式版,所以一直也不想过早的躺浑水,但是最近无意中看到几篇文章,再加上美团和咸鱼等app也一直在做灰度测试,所以上周开始看了一下官方文档,地址:https://flutter.io/docs/get-started/install,然后在此做一下总结。

深入理解Flutter多线程

Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。

Flutter1.5 开始,将成为全平台 UI 框架!

Flutter 1.5 的发布,同期也宣布发布 Flutter for Web 的 Preview 版本,正式开启了 Flutter 的全平台 UI 框架之路。早在年初发布的 Flutter 2019 Roadmap 中,就有提到,会在今年支持移动设备之外的平台,对 Web 的支持,算是完成了一个新的里程碑吧。

Flutter支持Web开发了!

Flutter作为一个可移植的UI框架,已经支持现代Web应用开发了!我们很开心已经发布了SDK预览版,这样你可以在Web浏览器里直接运行你的Flutter UI代码。

Flutter 混合开发 (交互通信)

Flutter 与原生之间的通信依赖灵活的消息传递方式:1,Flutter 部分通过平台通道将消息发送到其应用程序的所在的宿主环境(原生应用)。2,宿主环境通过监听平台通道,接收消息。

Flutter 局部路由实现

Flutter是借鉴React的开发思想实现的,在子组件的插槽上,React有this.props.children,Vue有<slot></slot>。当然Flutter也有类似的Widget,那就是Navigator,不过是以router的形式实现(像<router-view></router-view>)。

Flutter Kotlin 到底该如何选择?

这两个技术在当下如何选择,我之前在公众号上的回复是:如果你已经处于一个比较满意的公司,并考虑长期发展,公司并未使用这两个技术,你可以专心钻研公司当下使用的,或者未来将要使用的,这些才能助你在公司步步高升。

Flutter 与 iOS 原生 WebView 对比

本文对比的是 UIWebView、WKWebView、flutter_webview_plugin(在 iOS 中使用的是 WKWebView)的加载速度,内存使用情况。测试网页打开的速度,只需要获取 WebView 在开始加载网页和网页加载完成时的时间戳

Flutter For Web

用来构建漂亮、定制化应用的跨平台的 UI 框架 Flutter 现在已经支持 Web 开发了。我们很高兴推出了一个预览版的 SDK 可以让开发者直接使用 Flutter UI 和业务逻辑代码构建 web 应用

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!