Flutter實(shí)戰(zhàn) 異步UI更新(FutureBuilder、StreamBuilder)

2022-05-10 11:05 更新

很多時(shí)候我們會(huì)依賴一些異步數(shù)據(jù)來(lái)動(dòng)態(tài)更新 UI,比如在打開一個(gè)頁(yè)面時(shí)我們需要先從互聯(lián)網(wǎng)上獲取數(shù)據(jù),在獲取數(shù)據(jù)的過(guò)程中我們顯示一個(gè)加載框,等獲取到數(shù)據(jù)時(shí)我們?cè)黉秩卷?yè)面;又比如我們想展示 Stream(比如文件流、互聯(lián)網(wǎng)數(shù)據(jù)接收流)的進(jìn)度。當(dāng)然,通過(guò) StatefulWidget 我們完全可以實(shí)現(xiàn)上述這些功能。但由于在實(shí)際開發(fā)中依賴異步數(shù)據(jù)更新 UI 的這種場(chǎng)景非常常見(jiàn),因此 Flutter 專門提供了FutureBuilderStreamBuilder兩個(gè)組件來(lái)快速實(shí)現(xiàn)這種功能。

#7.5.1 FutureBuilder

FutureBuilder會(huì)依賴一個(gè)Future,它會(huì)根據(jù)所依賴的Future的狀態(tài)來(lái)動(dòng)態(tài)構(gòu)建自身。我們看一下FutureBuilder構(gòu)造函數(shù):

FutureBuilder({
  this.future,
  this.initialData,
  @required this.builder,
})

  • futureFutureBuilder依賴的Future,通常是一個(gè)異步耗時(shí)任務(wù)。

  • initialData:初始數(shù)據(jù),用戶設(shè)置默認(rèn)數(shù)據(jù)。

  • builder:Widget 構(gòu)建器;該構(gòu)建器會(huì)在Future執(zhí)行的不同階段被多次調(diào)用,構(gòu)建器簽名如下:

  Function (BuildContext context, AsyncSnapshot snapshot) 

snapshot會(huì)包含當(dāng)前異步任務(wù)的狀態(tài)信息及結(jié)果信息 ,比如我們可以通過(guò)snapshot.connectionState獲取異步任務(wù)的狀態(tài)信息、通過(guò)snapshot.hasError判斷異步任務(wù)是否有錯(cuò)誤等等,完整的定義讀者可以查看AsyncSnapshot類定義。

另外,FutureBuilderbuilder函數(shù)簽名和StreamBuilderbuilder是相同的。

#示例

我們實(shí)現(xiàn)一個(gè)路由,當(dāng)該路由打開時(shí)我們從網(wǎng)上獲取數(shù)據(jù),獲取數(shù)據(jù)時(shí)彈一個(gè)加載框;獲取結(jié)束時(shí),如果成功則顯示獲取到的數(shù)據(jù),如果失敗則顯示錯(cuò)誤。由于我們還沒(méi)有介紹在 flutter 中如何發(fā)起網(wǎng)絡(luò)請(qǐng)求,所以在這里我們不真正去網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),而是模擬一下這個(gè)過(guò)程,隔3秒后返回一個(gè)字符串:

Future<String> mockNetworkData() async {
  return Future.delayed(Duration(seconds: 2), () => "我是從互聯(lián)網(wǎng)上獲取的數(shù)據(jù)");
}

FutureBuilder使用代碼如下:

...
Widget build(BuildContext context) {
  return Center(
    child: FutureBuilder<String>(
      future: mockNetworkData(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        // 請(qǐng)求已結(jié)束
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            // 請(qǐng)求失敗,顯示錯(cuò)誤
            return Text("Error: ${snapshot.error}");
          } else {
            // 請(qǐng)求成功,顯示數(shù)據(jù)
            return Text("Contents: ${snapshot.data}");
          }
        } else {
          // 請(qǐng)求未結(jié)束,顯示loading
          return CircularProgressIndicator();
        }
      },
    ),
  );
}

運(yùn)行結(jié)果如圖7-8、7-9所示:

圖7-8圖7-9

上面代碼中我們?cè)?code>builder中根據(jù)當(dāng)前異步任務(wù)狀態(tài)ConnectionState來(lái)返回不同的 widget。ConnectionState是一個(gè)枚舉類,定義如下:

enum ConnectionState {
  /// 當(dāng)前沒(méi)有異步任務(wù),比如[FutureBuilder]的[future]為null時(shí)
  none,


  /// 異步任務(wù)處于等待狀態(tài)
  waiting,


  /// Stream處于激活狀態(tài)(流上已經(jīng)有數(shù)據(jù)傳遞了),對(duì)于FutureBuilder沒(méi)有該狀態(tài)。
  active,


  /// 異步任務(wù)已經(jīng)終止.
  done,
}

注意,ConnectionState.active只在StreamBuilder中才會(huì)出現(xiàn)。

#7.5.2 StreamBuilder

我們知道,在 Dart 中Stream 也是用于接收異步事件數(shù)據(jù),和Future 不同的是,它可以接收多個(gè)異步操作的結(jié)果,它常用于會(huì)多次讀取數(shù)據(jù)的異步任務(wù)場(chǎng)景,如網(wǎng)絡(luò)內(nèi)容下載、文件讀寫等。StreamBuilder正是用于配合Stream來(lái)展示流上事件(數(shù)據(jù))變化的 UI 組件。下面看一下StreamBuilder的默認(rèn)構(gòu)造函數(shù):

StreamBuilder({
  Key key,
  this.initialData,
  Stream<T> stream,
  @required this.builder,
}) 

可以看到和FutureBuilder的構(gòu)造函數(shù)只有一點(diǎn)不同:前者需要一個(gè)future,而后者需要一個(gè)stream。

#示例

我們創(chuàng)建一個(gè)計(jì)時(shí)器的示例:每隔1秒,計(jì)數(shù)加1。這里,我們使用Stream來(lái)實(shí)現(xiàn)每隔一秒生成一個(gè)數(shù)字:

Stream<int> counter() {
  return Stream.periodic(Duration(seconds: 1), (i) {
    return i;
  });
}

StreamBuilder使用代碼如下:

 Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: counter(), //
      //initialData: ,// a Stream<int> or null
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        if (snapshot.hasError)
          return Text('Error: ${snapshot.error}');
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return Text('沒(méi)有Stream');
          case ConnectionState.waiting:
            return Text('等待數(shù)據(jù)...');
          case ConnectionState.active:
            return Text('active: ${snapshot.data}');
          case ConnectionState.done:
            return Text('Stream已關(guān)閉');
        }
        return null; // unreachable
      },
    );
 }

讀者可以自己運(yùn)行本示例查看運(yùn)行結(jié)果。注意,本示例只是為了演示StreamBuilder的使用,在實(shí)戰(zhàn)中,凡是 UI 會(huì)依賴多個(gè)異步數(shù)據(jù)而發(fā)生變化的場(chǎng)景都可以使用StreamBuilder

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)