對(duì)于剛接觸Flutter的同學(xué)來說,Stream(流)是一個(gè)相對(duì)比較抽象,也相對(duì)比較難以理解的東西。準(zhǔn)確的來說Stream并不是Flutter的特性嘉蕾,而是Dart語言自身所帶庫肪康。
Stream和Future都位于dart:async核心庫,是Dart中異步操作的兩大高手长踊。所以不僅僅可以用于Flutter,而是可以用于任何Dart語言上的實(shí)現(xiàn)。?
在我們剛開始學(xué)習(xí)Flutter的時(shí)候基本都是使用?StatefulWidget和setState((){})來刷新界面的數(shù)據(jù)谬返,當(dāng)我熟練使用流之后就可以基本完全使用StatelessWidget告別?StatefulWidget同樣達(dá)到數(shù)據(jù)刷新效果之斯。
Stream 分類: 單訂閱流和多訂閱流
單訂閱流(Single Subscription),這種流最多只能有一個(gè)監(jiān)聽器(listener)
多訂閱流(Broadcast),這種流可以有多個(gè)監(jiān)聽器監(jiān)聽(listener)
Stream 創(chuàng)建方式:
Stream.fromFuture 接收一個(gè)Future對(duì)象作為參數(shù)
Stream<String>.fromFuture(getData());
Future<String> getData() async{ await Future.delayed(Duration(seconds: 5)); return "返回一個(gè)Future對(duì)象";}??
Stream.fromIterable 接收一個(gè)集合對(duì)象作為參數(shù):
Stream<String>.fromFutures([getData()]);
Stream.periodic 接收一個(gè) Duration對(duì)象作為參數(shù)
Duration interval = Duration(seconds: 1);
Stream<int> stream = Stream<int>.periodic(interval);
操作方式及使用流程:
使用 Stream .periodic 方式創(chuàng)建一個(gè)Stream對(duì)象,用于創(chuàng)建一個(gè)周期發(fā)送事件的流遣铝,如下:
///用于創(chuàng)建一個(gè)每隔一秒發(fā)送一次無限事件的流,并打印出值
void _stream() async{
? ? Duration interval = Duration(seconds: 1);
? ? Stream<int> stream = Stream.periodic(interval, (data) => data);
? ? await for(int i in stream ){
? ? ? print(i);
? ? }
}
//(data){return data;} 上面的這句是用來回來值佑刷,如果省略這句,打印的值都為null
Stream.take(int count)
上面創(chuàng)建了一個(gè)無限每隔一秒發(fā)送一次事件的流酿炸,如果我們想指定只發(fā)送10個(gè)事件則瘫絮,用take。下面就只會(huì)打印出0-9
void _stream() async{
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10); //指定發(fā)送事件個(gè)數(shù)
? await for(int i in stream ){
? print(i);
? }
}
Stream.takeWhile
上面這種方式我們是只制定了發(fā)送事件的個(gè)數(shù)填硕,如果我們也不知道發(fā)送多少個(gè)事件麦萤,我們可以從返回的結(jié)果上做一個(gè)返回值的限制,上面結(jié)果也可以用以下方式實(shí)現(xiàn)
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
//? ? stream = stream.take(10);
? stream = stream.takeWhile((data) {
? ? return data < 10;
? });
? await for (int i in stream) {
? ? print(i);
? }
}
Stream.skip(int count)?
skip可以指定跳過前面的幾個(gè)事件,如下會(huì)跳過0和1,輸出 2-9;
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? stream = stream.skip(2);
? await for (int i in stream) {
? ? print(i);
? }
}
Stream.skipWhile?
可以指定跳過不發(fā)送事件的指定條件扁眯,如下跳過0-4的輸出壮莹,輸出5-9
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? stream = stream.skipWhile((data) => data<5);
? await for (int i in stream) {
? ? print(i);
? }
}
Stream.toList()?
將流中所有的數(shù)據(jù)收集存放在List中,并返回 Future<List>對(duì)象姻檀,listData里面 0-91.這個(gè)是一個(gè)異步方法命满,要結(jié)果則需要使用await關(guān)鍵字
這個(gè)是等待Stream當(dāng)流結(jié)束時(shí),一次返回結(jié)果
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? List<int> listData = await stream.toList();
? for (int i in listData) {
? ? print(i);
? }
}
Stream. listen()
這是一種特定的可以用于監(jiān)聽數(shù)據(jù)流的方式绣版,和 forEach循環(huán)的效果一致胶台,但是返回的是StreamSubscription<T>對(duì)象,如下也會(huì)輸出0-9,同時(shí)打印出 ”流已完成“
看一下源碼這種方式可以接收
StreamSubscription<T> listen(void onData(T event),
? ? ? {Function onError, void onDone(), bool cancelOnError});
1.onData是接收到數(shù)據(jù)的處理,必須要實(shí)現(xiàn)的方法
2.onError流發(fā)生錯(cuò)誤時(shí)候的處理
3.onDone流完成時(shí)候調(diào)取
4.cancelOnError發(fā)生錯(cuò)誤的時(shí)候是否立馬終止
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? stream.listen((data) {
? ? print(data);
? }, onError: (error) {
? ? print("流發(fā)生錯(cuò)誤");
? }, onDone: () {
? ? print("流已完成");
? }, cancelOnError: false);
}
Stream. forEach()
這中操作和listen()的方式基本差不多杂抽,也是一種監(jiān)聽流的方式诈唬,這只是監(jiān)聽了onData,下面代碼也會(huì)輸出0-9
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? stream.forEach((data) {
? ? print(data);
? });
}
Stream .length
用于獲取等待流中所有事件發(fā)射完成之后統(tǒng)計(jì)事件的總數(shù)量,下面代碼會(huì)輸出 10
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream.periodic(interval, (data) => data);
? stream = stream.take(10);
? var allEvents = await stream.length;
? print(allEvents);
}
Stream.where
在流中添加篩選條件,過濾掉一些不想要的數(shù)據(jù)缩麸,滿足條件返回true,不滿足條件返回false,如下我們篩選出流中大于5小于10的數(shù)據(jù)
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream<int>.periodic(interval, (data) => data);
? stream = stream.where((data)=>data>5);
? stream = stream.where((data)=> data<10);
? await for(int i in stream){
? ? print(i);
? }
}
stream.map
對(duì)流中的數(shù)據(jù)進(jìn)行一些變換铸磅,以下是我對(duì)Stream的每個(gè)數(shù)據(jù)都加1
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream<int> stream = Stream<int>.periodic(interval, (data) => data);
? stream = stream.map((data) => data + 1);
? await for (int i in stream) {
? ? print(i);
? }
}
Stream.expand
對(duì)流中的數(shù)據(jù)進(jìn)行一個(gè)擴(kuò)展,如下,會(huì)輸出1,1,2,2,3,3….
void _stream() async {
? Duration interval = Duration(seconds: 1);
? Stream stream = Stream.periodic(interval, (data) => data);
? stream = stream.expand((data)=>[data,data]);
? stream.listen((data)=>print(data),onError:(error)=> print("發(fā)生錯(cuò)誤") );
}
Stream.transform
如果我們?cè)谠诹髁鬓D(zhuǎn)的過程中需要進(jìn)行一些轉(zhuǎn)換和控制我們則需要使用到transform,接收一個(gè)StreamTransformer<S,T>愚屁,S表示轉(zhuǎn)換之前的類型济竹,T表示轉(zhuǎn)換后的輸入類型,如下代碼我們會(huì)接收到三組數(shù)字模擬輸入了三次密碼霎槐,并判斷真確的密碼,同時(shí)輸出密碼正確和密碼錯(cuò)誤:
void _stream() async {
? var stream = Stream<int>.fromIterable([123456,234567,678901]);
? var st = StreamTransformer<int, String>.fromHandlers(
? ? ? handleData: (int data, sink) {
? ? if (data == 678901) {
? ? ? sink.add("密碼輸入正確,正在開鎖梦谜。丘跌。。");
? ? } else {
? ? ? sink.add("密碼輸入錯(cuò)誤...");
? ? }
? });
? stream.transform(st).listen((String data) => print(data),
? ? ? onError: (error) => print("發(fā)生錯(cuò)誤"));
}
輸入如下結(jié)果 :
I/flutter (18980): 密碼輸入錯(cuò)誤...??
I/flutter (18980): 密碼輸入錯(cuò)誤...
I/flutter (18980): 密碼輸入正確,正在開鎖唁桩。闭树。。?
StreamController使用
構(gòu)建單訂閱的Streamcontroller
//StreamController里面會(huì)創(chuàng)建一個(gè)Stream荒澡,我們實(shí)際操控的Stream
StreamController<String> streamController = StreamController();
streamController.stream.listen((data)=> print(data));
streamController.sink.add("aaa");
streamController.add("bbb");
streamController.add("ccc");
streamController.close();
//上面代碼我們會(huì)輸出 aaa,bbb,ccc
注意:如果我們給上面的代碼再加一個(gè)listen會(huì)報(bào)如下異常,所以單訂閱流报辱,只能有一個(gè)listen。一般情況下我們多數(shù)都是使用的單訂閱流单山,我們也可以將單訂閱流轉(zhuǎn)成多訂閱流碍现。
構(gòu)建多監(jiān)聽器的StreamController有兩種方式
1.直接創(chuàng)建多訂閱Stream
StreamController<String> streamController = StreamController.broadcast();
? streamController.stream.listen((data){
? ? print(data);
? },onError: (error){
? ? print(error.toString());
? });
? streamController.stream.listen((data) => print(data));
? streamController.add("bbb");
? //上面代碼回輸出 bbb,bbb
2.將單訂閱流轉(zhuǎn)成多訂閱流
? StreamController<String> streamController = StreamController();
Stream stream =streamController.stream.asBroadcastStream();
stream.listen((data) => print(data));
stream.listen((data) => print(data));
streamController.sink.add("aaa");
streamController.close();
//上面代碼會(huì)輸出 aaa,aaa
注意:在流用完了之后記得關(guān)閉,調(diào)用streamController.close()
前面我把Stream的常用方式做了簡(jiǎn)單的介紹和演示米奸,我們?cè)趺唇Y(jié)合Flutter使用呢昼接?在Flutter里面提供了一個(gè)Widget名叫StreamBuilder,StreamBuilder其實(shí)是個(gè)StreamBuilder它一直記錄著流中最新的數(shù)據(jù),當(dāng)數(shù)據(jù)流發(fā)生變化時(shí)悴晰,會(huì)自動(dòng)調(diào)用builder方法進(jìn)行重建慢睡。
StreamBuilder的源碼如下,需要接受一個(gè)流铡溪,我們可以傳入一個(gè)StreamController的Stream
const StreamBuilder({
? Key key,
? this.initialData,
? Stream<T> stream,
? @required this.builder,
}) : assert(builder != null),
? ? super(key: key, stream: stream);
使用StreamController?結(jié)合?StreamBuider對(duì)官方的計(jì)數(shù)器進(jìn)行改進(jìn)漂辐,取代setState刷新頁面,代碼如下
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _count = 0;
final StreamController<int> _streamController = StreamController();
@override
Widget build(BuildContext context) {
? return Scaffold(
? ? body: Container(
? ? ? child: Center(
? ? ? ? child: StreamBuilder<int>(
? ? ? ? ? ? stream: _streamController.stream,
? ? ? ? ? ? builder: (BuildContext context, AsyncSnapshot snapshot) {
? ? ? ? ? ? ? return snapshot.data == null
? ? ? ? ? ? ? ? ? ? Text("0")
? ? ? ? ? ? ? ? ? : Text("${snapshot.data}");
? ? ? ? ? ? }),
? ? ? ),
? ? ),
? ? floatingActionButton: FloatingActionButton(
? ? ? ? child: const Icon(Icons.add),
? ? ? ? onPressed: () {
? ? ? ? ? _streamController.sink.add(++_count);
? ? ? ? }),
? );
}
@override
void dispose() {
? _streamController.close();
? super.dispose();
}
}
源碼相關(guān)
從StreamController源碼我們可以看出來里面創(chuàng)建的得過程 默認(rèn)創(chuàng)建一個(gè)_SyncStreamController,具體可以去讀取下源碼棕硫,看看StreamController是怎么創(chuàng)建Stream和StreamSink
factory StreamController(
? ? ? {void onListen(),
? ? ? void onPause(),
? ? ? void onResume(),
? ? ? onCancel(),
? ? ? bool sync: false}) {
? ? return sync
? ? ? ? ? new _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
? ? ? ? : new _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
? }
注意:上面我既使用了streamController.sink.add("aaa");添加數(shù)據(jù)髓涯,也使用了streamController.add("bbb");方式添加數(shù)據(jù)。實(shí)際效果是一樣的饲帅,查看源碼可知 sink如下,實(shí)際上sink是對(duì)StreamController的一種包裝复凳,最終都是調(diào)取的StreamController.add方法;
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() => _target.close();
? Future addStream(Stream<T> source) => _target.addStream(source);
? Future get done => _target.done;
以上是對(duì)Flutter中Stream使用的一些簡(jiǎn)單的認(rèn)識(shí)和常用方法的總結(jié),StreamBuilder和?StreamController結(jié)合使用灶泵,實(shí)現(xiàn)局部刷新效果(比喻一個(gè)頁面有多個(gè)接口育八,各自應(yīng)該刷新自己控制的UI的部分而不應(yīng)該整個(gè)頁面刷新,時(shí)候我們可以使用StreamController和StreamBuilder達(dá)到這種效果)如有不到之處或者有偏差的地方赦邻,望指正……