Dart基础之Stream

1,472 阅读3分钟

如何获取Stream

periodic

void main() {
  stream_periodic();

  //stream_take();

  //stream_takeWhile();

  //stream_skip();

  //stream_skipWhile();

  //stream_toList();
}

int _periodic_callback(int value) {
  return 2 * value;
}

stream_periodic() async {
  Duration _duration = Duration(seconds: 2);
  /**
   * 官方注释:
   * Creates a stream that repeatedly emits events at [period] intervals.
   *
   * The event values are computed by invoking [computation]. The argument to
   * this callback is an integer that starts with 0 and is incremented for
   * every event.
   *
   * If [computation] is omitted the event values will all be `null`.
   *
   * 大体意思就是:
   *
   * _periodic_callback方法计算处理后的值(起始值为0,不断递增),每隔2秒不断的生成包含int结果的事件,
   *
   * 如果省略了回调,则事件值将全部为null
   *
   *
   */
  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);

  //1.
  //int v = await stream.first;
  //print(v);

  //2.
  await for (int value in stream) {
    print("=>$value");
  }

  //3. 比方法2友好很多
  stream.listen((value) {
    print("===>$value");
  });

  /**
   *  1与2或3不能同时解开注释,但2和3 可以同时解开注释,但只会输出监听2;
   *
   *  因为2和3其实是一样的,单监听一次之后则事件消费
   */
}

输出:

=>0
=>2
=>4
=>6
=>8
=>10
=>12
=>14
=>16
=>18
=>20
=>22
...

无限的输出。。。

注释1与2同时解开之后,会报

Unhandled exception:
Bad state: Stream has already been listened to.

的错误,报错信息也很简单,就是已经被监听过的流不能再次被监听了,其实这也是单订阅流的特点。下面会有一个广播流。

fromFuture

stream_fromFuture() async {
  print("start");
  Future<String> _future = Future(() {
    sleep(Duration(seconds: 2));
    return "Sleep 2 秒";
  });
  //从Future创建一个Stream,可见只能穿一个Future
  Stream<String> stream = Stream.fromFuture(_future);

  stream.listen((value) {
    print("=>${value}");
  });
  print("end");
}

输出:

start
end
=>Sleep 2 秒

当Future任务执行完成后,将结果放入Stream中,而后从Stream中将任务完成的结果取出。这种用法,很像异步任务队列。

fromFutures

stream_fromFutures() async {
  Future<String> _future1 = Future(() {
    return "我没睡";
  });

  Future<String> _future2 = Future(() {
    sleep(Duration(seconds: 2));
    return "Sleep 2 秒";
  });

  Stream<String> stream = Stream.fromFutures([_future2, _future1]);

  stream.listen((value) {
    print("=>${value}");
  });
}

输出:

=>我没睡
=>Sleep 2 秒

fromIterable

stream_fromIterable() async{

  Stream<String> stream =Stream.fromIterable(["路人甲","炮灰乙","流氓丙"]);

  stream.listen((value){
    print("=>${value}");
  });

}

输出:

=>路人甲
=>炮灰乙
=>流氓丙

Stream 里面的一些方法

listen

监听流,前面一直在用的。

take

截取一定数量的Stream。

stream_take() async {
  Duration _duration = Duration(seconds: 2);

  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);

  //只截取了前5个事件,之后Stream会关闭
  stream = stream.take(5);

  stream.listen((value) {
    print("===>$value");
  });
}

输出:

===>0
===>2
===>4
===>6
===>8

takeWhile

stream_takeWhile() async {
  Duration _duration = Duration(seconds: 2);
  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);
  //筛选符合条件的事件
  stream = stream.takeWhile(condition);

  stream.listen((value) {
    print("===>$value");
  });
}

bool condition(int value) {
  //不满足条件的不监听
  return value < 12;
}

输出:

===>0
===>2
===>4
===>6
===>8
===>10

skip

stream_skip() async {
  Duration _duration = Duration(seconds: 2);
  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);

  stream = stream.take(10);

  //跳过前两个事件
  stream = stream.skip(2);

  stream.listen((value) {
    print("===>$value");
  });
}

输出:

===>4
===>6
===>8
===>10
===>12
===>14
===>16
===>18

stream_skipWhile

stream_skipWhile() async {
  Duration _duration = Duration(seconds: 2);
  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);

  stream = stream.take(10);
  //跳转到满足值得事件
  stream = stream.skipWhile(condition);
  stream.listen((value) {
    print("===>$value");
  });
}
bool condition(int value) {
  //不满足条件的不监听
  return value < 12;
}

skipWhile 和takeWhile的用法是一样,都是筛选满足条件的Stream。

toList

stream_toList() async {
  Duration _duration = new Duration(seconds: 2);
  Stream<int> stream = Stream.periodic(_duration, _periodic_callback);
  stream = stream.take(5);
  //因为是异步的,所以有 await,返回已经完成的事件列表
  List<int> datas = await stream.toList();

  for (int i in datas) {
    print("===>$i");
  }
}

Future<List> toList() 表示将Stream中所有数据存储在List中。

length

取得Stream中所有数据的长度。

我们发现这个Stream这个流控制起来不太方便,不能想在指定的时刻投送事件或者关闭流,这个时候StreamController就起到了重要的辅助作用。

StreamController

import 'dart:async';

void main() {
//初始化"单订阅"流控制器
  StreamController ctrl = new StreamController();

  //初始化一个subscription 订阅者
  StreamSubscription subscription = ctrl.stream
      .listen(onListen,
              onError: onError,
              onDone: onDone,
              cancelOnError: false);//cancelOnError 当有Error的时候是否关闭流

//流流入数据,能够控制何时投递消息
  ctrl.sink.add('Hello World');
  ctrl.sink.add(1234);
  ctrl.sink.addError("onError!");
  ctrl.sink.add(13.14);
  ctrl.close();
}

void onListen(event) {
  print("==>${event}");
}

void onError(error) {
  print(error);
}

void onDone() {
  print('The stream is done !');
}

输出:

==>Hello World
==>1234
onError!
==>13.14
The stream is done !
  • onData

监听流事件。

  • onError

监听error的事件流。

  • onDone

当一个 Stream 调用close()方法后,发送了 done 事件,这个方法会被调用。

  • cancelOnError

当 Stream 碰到 Error 事件的时候,是否关闭这个 Stream。

广播流

其实也好理解,单订阅流就是流里面的东西是第一个订阅者私有的,不允许他人染指,而广播流,则表示流里面的东西是公有的。

import 'dart:async';

//广播流
void main() {
  StreamController<int> ctrl = new StreamController<int>.broadcast();

// 第一个订阅者
  StreamSubscription subscription1 =
      ctrl.stream.listen((value) => print('第一个订阅者 $value'));

  // 第二个订阅者
  StreamSubscription subscription2 =
      ctrl.stream.listen((value) => print('第二个订阅者 $value'));

  ctrl.sink.add(2);
  ctrl.sink.add(4);

  ctrl.close();
}

输出:

第一个订阅者 2
第二个订阅者 2
第一个订阅者 4
第二个订阅者 4

StreamTransformer

流的转换,流现在可以看做是一个传送数据的管道,或者传送带,在管道的一端投送事件,在另一端接受事件中的数据,但是在这一管道传送过程中数据是可以转换的。传入的是int类型,输出的是double类型,这就是转换。

map 转换

import 'dart:async';

void main() {
  StreamController<int> ctl = StreamController<int>();

  ctl.stream
      .map((value) => Data((value * 2).toString())) // int 转换为 Data 类型
      .listen((value) => print(value));

  ctl.sink.add(3);

  ctl.close();
}

class Data {
  final String name;

  Data(this.name);

  @override
  String toString() {
    // TODO: implement toString
    return "data=> ${this.name}";
  }
}

输出:

data=> 6

StreamTransformer.fromHandlers

import 'dart:async';

//由int类型转换为Data类型
void main() {
  StreamController ctl = StreamController<int>();

  // 创建 StreamTransformer对象
  StreamTransformer stf = StreamTransformer<int, Data>.fromHandlers(
    handleData: (int data, EventSink sink) {
      // 操作数据后,转换为 Data 类型
      sink.add(Data((data * 2).toString()));
    },
  );
  // 调用流的transform方法,传入转换对象
  Stream stream = ctl.stream.transform(stf);
  stream.listen(print);

  // 添加数据,这里的类型是int
  ctl.add(3);

  // 调用后,触发handleDone回调
  ctl.close();
}

class Data {
  final String name;

  Data(this.name);

  @override
  String toString() {
    // TODO: implement toString
    return "data=> ${this.name}";
  }
}

结果都是一样的。

Stream 在Flutter中的运用

Stream可以刷新 StatelessWidget控件里面的UI。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: HomePage(),
    title: 'Stream Demo',
  ));
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stream Demo'),
      ),
      body: Center(
        child: StreamBuilder(
          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,
              ),
            );
          },
          initialData: 0,
          stream: _stream(),
        ),
      ),
    );
  }

  Stream<int> _stream() {
    Duration interval = Duration(seconds: 1);
    Stream<int> stream = Stream<int>.periodic(interval, transform);
    stream = stream.take(59);
    return stream;
  }
  int transform(int value) {
    return value;
  }
}

效果图

HomePage继承一个无状态的组件,是无法用setState刷新界面的,但是Stream可以帮我们在无状态组件中刷新UI。

总结

Stream可以看作是一条管道,管道的中间有一个加工房,通过sink在管道中投放事件,加工房通过map,StreamTransformer可以对管道中的数据进行加工转换,最后由listen监听处理结果。其中涉及到的类:

  • Stream

流,事件(数据)的包裹类。

  • StreamController

Stream的控制管理类

  • StreamSink

事件的输入口(sink.add)

  • StreamSubscription

订阅者