探索Flutter中的流

原文地址:https://medium.com/flutterpub/exploring-streams-in-flutter-4732e5524dd8

這篇文章主要介紹Flutter中的流是什么以及如何使用。

流是什么

正如Flutter文檔上所說的,流提供異步的數(shù)據(jù)序列羞延,接下來從真實(shí)世界中取了解什么是流雨席。

披薩屋的故事

曾幾何時(shí)奔缠,有一個(gè)人John非常擅長做pizza滓窍,他制作了很多不同種類的披薩碍庵,他制作的披薩很好吃度气,以至于世界各地的人都慕名而來割按,John有一個(gè)小房子用來接受客戶的訂單然后制作披薩給客戶。John有一個(gè)女兒Mia來幫助他做生意磷籍,John采用了一個(gè)非常簡單且有效的方式來開展業(yè)務(wù)适荣。

  1. Mia從客戶手里拿到訂單并轉(zhuǎn)給John
  2. John檢查訂單看是否有配料以供烘烤
  3. 客戶將在收集室領(lǐng)取披薩,如果沒有配料院领,他們將會被通知“你的披薩無法制作的”弛矛。

接下來將列出一些有趣的東西用來有效的理解Stream的概念。

  1. 烘烤披薩的房子栅盲。房子是一個(gè)固定的地方汪诉,所以只會創(chuàng)建一次,用來接受訂單然后烘烤披薩谈秫。

  2. Mia的唯一責(zé)任就是接受命令然后傳遞給John扒寄。

  3. John是決策者,他將決定披薩是否可以烘烤拟烫,然后將披薩送到收集室该编。

因此,總的來說硕淑,數(shù)據(jù)按順序流入流中课竣,然后經(jīng)過處理嘉赎,并發(fā)送到輸出流,接下來我們將上面的故事轉(zhuǎn)換成代碼于樟。

Dart中的流

當(dāng)我們談?wù)揇art中的流時(shí)公条,我們需要使用Dart提供的async庫,該庫支持異步編程迂曲,并包含創(chuàng)建Streams的所有類和方法靶橱。

頁面中有四個(gè)Button,每一個(gè)Button都代表一中披薩種類路捧,如果你點(diǎn)擊某一個(gè)按鈕关霸,就會把訂單給Mia,然后Mia將訂單轉(zhuǎn)給John杰扫,接著John就會制作披薩队寇,然后將披薩放到收集屋,然后客戶可以來這里取他的披薩章姓。如果材料沒有的話佳遣,他可能會收到缺貨的消息。

開始寫代碼

  1. 創(chuàng)建Flutter項(xiàng)目啤覆,如果不知道怎么做苍日,參考這里

  2. 移除main.dart中的所有代碼,然后添加下面的代碼:

import 'package:flutter/material.dart';
import 'src/app.dart';

void main() => runApp(new MyApp());
  1. 此時(shí)會出現(xiàn)錯(cuò)誤窗声,先不要管相恃,我們接下來會修復(fù)它。
  2. 在lib包下面創(chuàng)建一個(gè)src包笨觅,接著在src包中創(chuàng)建一個(gè)app.dart文件拦耐,接著編寫下面的代碼:
import 'package:flutter/material.dart';
import 'pizza_house.dart';
import 'blocs/provider.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider( 
      child: MaterialApp(
        home: new PizzaHouse(),
      ),
    );
  }
}
  1. 在src包下創(chuàng)建pizza_house.dart, 現(xiàn)在先不寫任何東西在里面。
  2. 在src包下創(chuàng)建bloc包见剩,在該包下創(chuàng)建兩個(gè)文件杀糯,分別是bloc.dartprovider.dart, 下面是provider.dart中的代碼
import 'package:flutter/material.dart';
import 'bloc.dart';
export 'bloc.dart';

class Provider extends InheritedWidget {
  final bloc = Bloc();

  Provider({Key key, Widget child}) : super(key: key, child: child);

  bool updateShouldNotify(_) => true;

  static Bloc of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;
  }
}

該類主要負(fù)責(zé)在組件中對Bloc的訪問。下面是項(xiàng)目結(jié)構(gòu)苍苞。

image.png
  1. 接下來實(shí)現(xiàn)bloc.dart,下面就是bloc的完整代碼:
import 'dart:async';

class Bloc {

  //Our pizza house
  final order = StreamController<String>();

  //Our collect office
  Stream<String> get orderOffice => order.stream.transform(validateOrder);

  //Pizza house menu and quantity
  static final _pizzaList = {
    "Sushi": 2,
    "Neapolitan": 3,
    "California-style": 4,
    "Marinara": 2
  };

  //Different pizza images
  static final _pizzaImages = {
    "Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",
    "Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",
    "California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",
    "Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"
  };


  //Validate if pizza can be baked or not. This is John
  final validateOrder =
      StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {
    if (_pizzaList[order] != null) {
      //pizza is available
      if (_pizzaList[order] != 0) {
        //pizza can be delivered
        sink.add(_pizzaImages[order]);
        final quantity = _pizzaList[order];
        _pizzaList[order] = quantity-1;
      } else {
        //out of stock
        sink.addError("Out of stock");
      }
    } else {
      //pizza is not in the menu
      sink.addError("Pizza not found");
    }
  });

  //This is Mia
  void orderItem(String pizza) {
    order.sink.add(pizza);
  }
}

StreamController

上面的代碼主要是用來接收訂單固翰,處理訂單并且輸出。但是有什么方法可以使StreamController成為一個(gè)完整的披薩屋羹呵,有哪些方法可以用來接收訂單骂际,處理訂單和輸出訂單。

image.png

StreamController有兩個(gè)getter冈欢,一個(gè)是sink歉铝,另一個(gè)是stream.sink是Mia在這里接受訂單并傳遞給流,還句話說就是sink會將數(shù)據(jù)添加到StreamController的流中凑耻,現(xiàn)在我們來說一下Stream太示,它會在做一些處理之后將數(shù)據(jù)傳遞給外界柠贤。下面的代碼就是StreamController的接收器和Stream。

  //Our pizza house
  final order = StreamController<String>();

  //Our collect office
  Stream<String> get orderOffice => order.stream.transform(validateOrder);
  //This is Mia
  void orderItem(String pizza) {
    order.sink.add(pizza);
  }

接收器有一個(gè)add的方法类缤,它將數(shù)據(jù)添加到流中臼勉,這里是順序添加到流中。orderOffice是一個(gè)getter用來在pizza_house.dart中調(diào)用呀非,以便將輸出提供給客戶坚俗。
在上面的代碼中,你肯定想知道transform的用途岸裙,他將調(diào)用John處理的訂單,然后將處理后的數(shù)據(jù)傳入到流中速缆。John在代碼中是validateOrder降允,validateOrder是StreamTransformer。

//Validate if pizza can be baked or not. This is John
  final validateOrder =
      StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {
    if (_pizzaList[order] != null) {
      //pizza is available
      if (_pizzaList[order] != 0) {
        //pizza can be delivered
        sink.add(_pizzaImages[order]);
        final quantity = _pizzaList[order];
        _pizzaList[order] = quantity-1;
      } else {
        //out of stock
        sink.addError("Out of stock");
      }
    } else {
      //pizza is not in the menu
      sink.addError("Pizza not found");
    }
  });

StreamTransformer

簡單的來說艺糜,他將從流中接收傳入的訂單剧董,并檢查訂單是否有效。如果訂單有效破停,它將使用sink.add(successOrder)(pizza pizza successfully)方法將輸出添加到流中翅楼。如果訂單無效,它將會添加sink.addError(invalidOrder)真慢,告訴客戶“您訂購的披薩缺貨”毅臊。

//Pizza house menu and quantity
  static final _pizzaList = {
    "Sushi": 2,
    "Neapolitan": 3,
    "California-style": 4,
    "Marinara": 2
  };

  //Different pizza images
  static final _pizzaImages = {
    "Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",
    "Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",
    "California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",
    "Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"
  };

_pizzaList是我們的菜單,它將保存比薩餅的類型以及可以烘烤的總量黑界。 _pizzaImages是一個(gè)map管嬉,上面有比薩餅屋烤制的不同種類的比薩的圖片。
最后我們?nèi)?shí)現(xiàn)pizza_house.dart類朗鸠。

import 'package:flutter/material.dart';
import 'blocs/provider.dart';

class PizzaHouse extends StatelessWidget {
  var pizzaName = "";
  @override
  Widget build(BuildContext context) {
    final _bloc = Provider.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("Pizza House"),
      ),
      body: Container(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[menu1(_bloc), menu2(_bloc), orderOffice(_bloc)],
        ),
      ),
    );
  }

  menu1(Bloc bloc) {
    return Container(
      margin: EdgeInsets.all(8.0),
      child: Row(
        children: <Widget>[
          Expanded(
            child: RaisedButton(
              child: Text("Neapolitan"),
              onPressed: () {
                bloc.orderItem("Neapolitan");
                pizzaName = "Neapolitan";
              },
            ),
          ),
          Container(
            margin: EdgeInsets.only(left: 2.0, right: 2.0),
          ),
          Expanded(
            child: RaisedButton(
              child: Text("California-style"),
              onPressed: () {
                bloc.orderItem("California-style");
                pizzaName = "California-style";
              },
            ),
          )
        ],
      ),
    );
  }

  menu2(Bloc bloc) {
    return Container(
      margin: EdgeInsets.all(8.0),
      child: Row(
        children: <Widget>[
          Expanded(
            child: RaisedButton(
              child: Text("Sushi"),
              onPressed: () {
                bloc.orderItem("Sushi");
                pizzaName = "Sushi";
              },
            ),
          ),
          Container(
            margin: EdgeInsets.only(left: 2.0, right: 2.0),
          ),
          Expanded(
            child: RaisedButton(
              child: Text("Marinara"),
              onPressed: () {
                bloc.orderItem("Marinara");
                pizzaName = "Marinara";
              },
            ),
          )
        ],
      ),
    );
  }

  orderOffice(Bloc bloc) {
    return StreamBuilder(
      stream: bloc.orderOffice,
      builder: (context, AsyncSnapshot<String> snapshot) {
        if (snapshot.hasData) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Image.network(
                snapshot.data,
                fit: BoxFit.fill,
              ),
              Container(
                margin: EdgeInsets.only(top: 8.0),
              ),
              Text("Yay! Collect your $pizzaName pizza")
            ],
          );
        } else if (snapshot.hasError) {
          return Column(
            children: <Widget>[
              Image.network("http://megatron.co.il/en/wp-content/uploads/sites/2/2017/11/out-of-stock.jpg",
              fit: BoxFit.fill),
              Text(
                snapshot.error,
                style: TextStyle(
                  fontSize: 20.0,
                ),
              ),
            ],
          );
        }
        return Text("No item in collect office");
      },
    );
  }
}

build方法中第一行是聲明bloc實(shí)例蚯撩,使用此實(shí)例,我們可以下訂單并檢查collect office總的輸出烛占。我們已經(jīng)將下不同類型的訂單的功能添加到了每個(gè)button的onPressed方法中胎挎,在onPressed方法中我們調(diào)用orderItem,然后會調(diào)用sink.add方法忆家。為了向客戶展示輸出的數(shù)據(jù)犹菇,這里我們需要使用StreamBuilder。

StreamBuilder將從orderOffice()方法中監(jiān)聽Stream弦赖,如果有任何可用數(shù)據(jù)项栏,它將根據(jù)來自流的數(shù)據(jù)返回一個(gè)widget,StreamBuilder有兩個(gè)參數(shù)蹬竖,一個(gè)是stream沼沈,另一個(gè)是builder流酬,stream接收一個(gè)從bloc中返回的一個(gè)流,即orderOffice列另。StreamBuilder將監(jiān)聽任何進(jìn)來的新數(shù)據(jù)芽腾,builder有兩個(gè)參數(shù),context和snapshot页衙,snapshot就像是數(shù)據(jù)提供者摊滔,它將從流中獲取數(shù)據(jù),以便我們可以處理它并返回要顯示的正確組件店乐。正如你看到的那樣艰躺,我們可以通過hasData來判斷是否有數(shù)據(jù),從而正確的顯示圖片或者文字眨八。

在整個(gè)應(yīng)用程序中沒有使用過一個(gè)StatefulWidget腺兴,仍然能夠改變應(yīng)用程序的狀態(tài)。換句話說廉侧,使用Streams能夠讓應(yīng)用程序被動反應(yīng)页响。當(dāng)您想要監(jiān)聽數(shù)據(jù)中的更改并根據(jù)數(shù)據(jù)做出反應(yīng)時(shí),流非常有用段誊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闰蚕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子连舍,更是在濱河造成了極大的恐慌没陡,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烟瞧,死亡現(xiàn)場離奇詭異诗鸭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)参滴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門强岸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砾赔,你說我怎么就攤上這事蝌箍。” “怎么了暴心?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵妓盲,是天一觀的道長。 經(jīng)常有香客問我专普,道長悯衬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任檀夹,我火速辦了婚禮筋粗,結(jié)果婚禮上策橘,老公的妹妹穿的比我還像新娘。我一直安慰自己娜亿,他們只是感情好丽已,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著买决,像睡著了一般沛婴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上督赤,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天嘁灯,我揣著相機(jī)與錄音,去河邊找鬼躲舌。 笑死旁仿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的孽糖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼毅贮,長吁一口氣:“原來是場噩夢啊……” “哼办悟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滩褥,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤病蛉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瑰煎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铺然,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年酒甸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魄健。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡插勤,死狀恐怖沽瘦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情农尖,我是刑警寧澤析恋,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站盛卡,受9級特大地震影響助隧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滑沧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一并村、第九天 我趴在偏房一處隱蔽的房頂上張望巍实。 院中可真熱鬧,春花似錦橘霎、人聲如沸蔫浆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓦盛。三九已至,卻和暖如春外潜,著一層夾襖步出監(jiān)牢的瞬間原环,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工处窥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘱吗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓滔驾,卻偏偏與公主長得像谒麦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子哆致,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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