哥哥手把手教你認(rèn)識(shí)Flutter Stream及其操作符使用

對(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á)到這種效果)如有不到之處或者有偏差的地方赦邻,望指正……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓棋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌按声,老刑警劉巖膳犹,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異签则,居然都是意外死亡须床,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門渐裂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豺旬,“玉大人,你說我怎么就攤上這事柒凉∽逶模” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵膝捞,是天一觀的道長(zhǎng)坦刀。 經(jīng)常有香客問我,道長(zhǎng)蔬咬,這世上最難降的妖魔是什么鲤遥? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮计盒,結(jié)果婚禮上渴频,老公的妹妹穿的比我還像新娘。我一直安慰自己北启,他們只是感情好卜朗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咕村,像睡著了一般场钉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懈涛,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天逛万,我揣著相機(jī)與錄音,去河邊找鬼批钠。 笑死宇植,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埋心。 我是一名探鬼主播指郁,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拷呆!你這毒婦竟也來了闲坎?” 一聲冷哼從身側(cè)響起疫粥,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腰懂,沒想到半個(gè)月后梗逮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绣溜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年慷彤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怖喻。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬欧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罢防,到底是詐尸還是另有隱情,我是刑警寧澤唉侄,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布咒吐,位于F島的核電站,受9級(jí)特大地震影響属划,放射性物質(zhì)發(fā)生泄漏恬叹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一同眯、第九天 我趴在偏房一處隱蔽的房頂上張望绽昼。 院中可真熱鬧,春花似錦须蜗、人聲如沸硅确。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菱农。三九已至,卻和暖如春柿估,著一層夾襖步出監(jiān)牢的瞬間循未,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工秫舌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留的妖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓足陨,卻偏偏與公主長(zhǎng)得像嫂粟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钠右,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容