沐鳴註冊_如何在Dart&Flutter中使用Stream

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功能的展示。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10046