Dart中的Stream初步研究

xmb發表於2021-09-02

官方文件中文翻譯:非同步程式設計:使用 stream

1、stream是什麼

Stream是一些列非同步事件的序列。

2、Stream的接收

① 使用非同步for迴圈

/// 使用非同步for迴圈來接收所有的stream
Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for(var value in stream) {
    sum += value;
  }
  return sum;
}
複製程式碼

② 使用streamlisten來監聽

3、stream的種類

① 單訂閱streamSingle-Subscription

只包含一個事件序列,事件需要按順序提供,不能丟失。比如讀取一個檔案,接收一個網頁。

② 廣播streamBroadcast

針對單個訊息的,一次處理一個訊息。比如瀏覽器的滑鼠事件。 可以在任何時候監聽,可以新增多個監聽,還可以隨時取消監聽。

4、處理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();
複製程式碼

5、修改stream的方法

修改stream會產生一個新的stream,在原來stream上新增的監聽,會轉到新的stream上,如果新的stream結束了,會轉到原來的stream上。

詳細參考文章:Flutter Stream簡介及部分操作符使用

// 將一個Stream轉為元素都為R型別的Stream
Stream<R> cast<R>();

// 把Stream中的每一個元素,轉換為一個序列sequence,比如元素1轉為[1, 1]
Stream<S> expand<S>(Iterable<S> Function(T element) convert);

// 按map的實現規則,轉換Stream中的每一個元素成為一個新的Stream
Stream<S> map<S>(S Function(T event) convert);

// 跳過前面count個事件
Stream<T> skip(int count);

// 根據傳入的條件規則,進行跳過
Stream<T> skipWhile(bool Function(T element) test);

// 指定只傳送count個事件
Stream<T> take(int count);

// 只傳送指定條件的事件
Stream<T> takeWhile(bool Function(T element) test);

// 用條件丟棄一些元素,建立一個新的Stream
Stream<T> where(bool Function(T event) test);

...
複製程式碼

6、listen方法

  StreamSubscription<T> listen(void onData(T event)?,
      {Function? onError, void onDone()?, bool? cancelOnError});
複製程式碼

7、stream的建立

① 從其他stream轉換 以上提到的修改stream的方法

② 使用非同步生成器(async*)生成stream

  /// 非同步生成器(async*) 生成 stream
  /// 通過 yield 和 yield* 向stream提交事件
  Stream<int> createAsyncStream() async* {
    int num = 0;
    while (true) {
      // 間隔1秒鐘
      await Future.delayed(Duration(seconds: 1));
      // 將num運算後的值放入stream
      yield num++;
      // 終止條件
      if (num == 10) break;
    }
  }
複製程式碼

③ 使用StreamController來建立

    var streamController = StreamController<int>(
      onListen: () {},
      onResume: () {},
      onPause: () {},
      onCancel: () {},
    );
複製程式碼

比如,event_bus的實現,使用廣播stream

  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);
複製程式碼

8、實際應用場景

event_bus 外掛的使用

相關文章:Flutter EventBus 的使用和底層實現分析

  1. 初始化EventBus,會建立一個通過廣播方式初始化的StreamController
  2. 訂閱監聽,on方法返回的是一個streamstreamlisten方法傳入的對監聽到事件的處理方法。
  3. fire方法實現,是通過往StreamController中新增自定義的事件。

Bloc外掛的使用

StreamBuilder元件

9、相關的語法關鍵字(await、async、sync*、async*、yield、yield*)

await用於等待非同步方法返回資料

async用於非同步方法

sync*多元素同步函式生成器,返回Iterable<T>

async*多元素非同步函式生成器,返回Stream<T>

yield傳送的是一個元素

yield*操作的是一個IterableStream

具體使用例子,如下:

import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: StreamPage(),
    );
  }
}

class StreamPage extends StatefulWidget {
  const StreamPage({Key? key}) : super(key: key);

  @override
  _StreamPageState createState() => _StreamPageState();
}

class _StreamPageState extends State<StreamPage> {
  late Stream<String> _stream;

  @override
  void initState() {
    super.initState();

    _stream = fetchEmojiStream(10);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Column(
            children: [
              MaterialButton(
                onPressed: test1,
                child: Text('測試1'),
              ),
              MaterialButton(
                onPressed: test2,
                child: Text('測試2'),
              ),
              MaterialButton(
                onPressed: test3,
                child: Text('測試3'),
              ),
              MaterialButton(
                onPressed: test4,
                child: Text('測試4'),
              ),
              MaterialButton(
                onPressed: test5,
                child: Text('測試5'),
              ),
            ],
          ),
          StreamBuilder(
            stream: _stream,
            builder: _builder,
          ),
        ],
      ),
    );
  }

  Widget _builder(BuildContext context, AsyncSnapshot snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        break;
      case ConnectionState.waiting:
        return CircularProgressIndicator();
      case ConnectionState.active:
        return Text(snapshot.requireData);
      case ConnectionState.done:
        return Text(snapshot.requireData);
    }
    return Container();
  }

  void test1() async {
    getEmoji(10).forEach(print);
    /*
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
     */
  }

  void test2() {
    getEmojiWithTime(10).forEach(print);
    /*
    flutter: ?-2021-09-02T16:41:24.557020
    flutter: ?-2021-09-02T16:41:24.558755
    flutter: ?-2021-09-02T16:41:24.559007
    flutter: ?-2021-09-02T16:41:24.559160
    flutter: ?-2021-09-02T16:41:24.559338
    flutter: ?-2021-09-02T16:41:24.559515
    flutter: ?-2021-09-02T16:41:24.559663
    flutter: ?-2021-09-02T16:41:24.559846
    flutter: ?-2021-09-02T16:41:24.560122
    flutter: ?-2021-09-02T16:41:24.560372
     */
  }

  void test3() {
    fetchEmoji(0).then(print);
    /*
    flutter: ?
     */
  }

  void test4() {
    fetchEmojiStream(10).listen(print);
    /* 每個一秒生成一個
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
    flutter: ?
     */
  }

  void test5() {
    fetchEmojiWithTime(10).forEach(print);
    /*
    flutter: ?-2021-09-02T17:15:21.591821
    flutter: ?-2021-09-02T17:15:22.595980
    flutter: ?-2021-09-02T17:15:23.601100
    flutter: ?-2021-09-02T17:15:24.608355
    flutter: ?-2021-09-02T17:15:25.610364
    flutter: ?-2021-09-02T17:15:26.612006
    flutter: ?-2021-09-02T17:15:27.619303
    flutter: ?-2021-09-02T17:15:28.623427
    flutter: ?-2021-09-02T17:15:29.626712
    flutter: ?-2021-09-02T17:15:30.629052
     */
  }

  /// 多元素同步
  /// 多元素同步函式生成器(sync*),返回Iterable
  /// yield 傳送一個元素
  Iterable<String> getEmoji(int count) sync* {
    Runes first = Runes('\u{1f47f}');
    for (int i = 0; i < count; i++) {
      yield String.fromCharCodes(first.map((e) => e + i));
    }
  }

  /// 多元素同步
  /// 多元素同步生成器(sync*),返回Iterable
  /// yield* 操作的是一個Iterable
  Iterable<String> getEmojiWithTime(int count) sync* {
    yield* getEmoji(10).map((e) => '$e-${DateTime.now().toIso8601String()}');
  }

  /// 單元素非同步
  /// await async
  Future<String> fetchEmoji(int count) async {
    await Future.delayed(Duration(milliseconds: 1000));
    return String.fromCharCodes(Runes('\u{1f47f}').map((e) => e + count));
  }

  /// 多元素非同步
  /// 多元素非同步生成器(async*),返回Stream
  /// yield操作的是一個元素
  Stream<String> fetchEmojiStream(int count) async* {
    for (int i = 0; i < count; i++) {
      yield await fetchEmoji(i);
    }
  }

  /// 多元素非同步
  /// 多元素非同步生成器(async*),返回Stream
  /// yield*操作的是一個Stream
  Stream<String> fetchEmojiWithTime(int count) async* {
    yield* fetchEmojiStream(count).map((event) => '$event-${DateTime.now().toIso8601String()}');
  }
}
複製程式碼

相關文章