Future.wait() 用于多个 Future 的等待。

47

我试图在我的设备没有网络连接时捕获错误。我已经构建了两个future方法,一个用于导入json,另一个用于查看数据库。我有一个future builder应该在两个futures完成之前等待,然后构建网格视图,但似乎由于连接错误,offlineFlashCardList被过早地调用了。有什么办法让它等待两个futures完成,然后才调用snapshot error吗?

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:baby_sound/strings.dart';
import 'package:baby_sound/objects/flashCardList.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'dart:async' show Future;
import 'dart:convert';
import 'package:baby_sound/database/database.dart';
import 'package:baby_sound/objects/network.dart';
import 'package:http/http.dart' as http;

class GridViewWidget extends StatefulWidget{
  @override
  createState() => new GridViewState();

}

class GridViewState extends State<GridViewWidget>{


  List<FlashCardList> flashCardList;
  List<FlashCardList> offlineFlashCardList;



  Future<List<FlashCardList>> fetchFlashCardList() async{
    debugPrint("before response");
    List<FlashCardList> tempFlashCardList;
    final response = await http.get('some json url');
    //checkConnection(url).then((response){
      debugPrint ("after database load, response code: ${response.statusCode}");
      if (response.statusCode == 200) {
        var data = json.decode(response.body);
        var flashCardListData = data["FlashCardList"] as List;
        tempFlashCardList = flashCardListData.map<FlashCardList>((json) => FlashCardList.fromJson(json)).toList();
        for (int i = 0; i < tempFlashCardList.length; i++){
          debugPrint("DBProvider listID: ${await DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID)}, flashCardID: ${tempFlashCardList[i].flashCardListID}");
          if (await DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID) == null){
            DBProvider.db.newFlashCardList(tempFlashCardList[i]);
            debugPrint("Adding ${tempFlashCardList[i].name}}}");
          } else {
            DBProvider.db.updateFlashCardList(tempFlashCardList[i]);
            debugPrint("Updating ${tempFlashCardList[i].name}, getFlashCardList: ${DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID)}");
          }
        }
        flashCardList = tempFlashCardList;
        debugPrint("Standard flashCardList Size: ${flashCardList.length}");
      }

    debugPrint("flashCardList Size Before Return: ${flashCardList.length}");
    return flashCardList;
    }

  Future<List<FlashCardList>> fetchFlashCardListFromDB() async{

    offlineFlashCardList = await DBProvider.db.getAllFlashCardListFromDB();
    debugPrint("fetchFromDB size: ${offlineFlashCardList.length}");
    return offlineFlashCardList;
  }



  @override
  void initState(){
    debugPrint ('debug main.dart');
    super.initState();

  }


  @override
  Widget build(BuildContext context){
    return new Scaffold(
      appBar: new AppBar(
        centerTitle: true,
        title: new Text(Strings.pageTitle),
      ),
      body: FutureBuilder<List<FlashCardList>>(
        future: new Future(() async{
          await fetchFlashCardList();
          await fetchFlashCardListFromDB();
        }),
        builder: (BuildContext context, AsyncSnapshot<List<FlashCardList>> snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.hasError) {
              debugPrint("Snapshot has error: ${snapshot.error}");
              return new GridView.builder(
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                      maxCrossAxisExtent: 200.0,
                      childAspectRatio: 0.5),
                  itemCount: offlineFlashCardList.length,
                  itemBuilder: (BuildContext context, int index) {
                    return _getGridItemUI(context, offlineFlashCardList[index]);
                  });
//              return new Center(child: new CircularProgressIndicator());
            } else {
              debugPrint("Grid ViewBuilder");
              return new GridView.builder(
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                      maxCrossAxisExtent: 200.0,
                      childAspectRatio:0.5),
                  itemCount: flashCardList.length,
                  itemBuilder: (BuildContext context, int index) {
                    return _getGridItemUI(context, flashCardList[index]);
                  });
            }
          }else {
            debugPrint("CircularProgress");
            return new Center(child: new CircularProgressIndicator());
          }
        })
    );
  }

  _getGridItemUI(BuildContext context, FlashCardList item){
    return new InkWell(
      onTap: () {
        _showSnackBar(context, item);
      },
      child: new Card(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[

              new Image(image: new CachedNetworkImageProvider("https://babymozart.org/babymozartq92C9TLa9UMkulL2m81xHdn9u2R92e1e/image/" + item.image)),
              /*new Expanded(
                child:new Center(
                  child: new Column(
                    children: <Widget>[
                      new SizedBox(height: 8.0),
                      new Expanded(
                        child: AutoSizeText(
                          item.name, maxLines: 1,
                        )
                      )
                    ],
                  ),
                )
              )*/
            ],
        ),
        elevation: 2.0,
        margin: EdgeInsets.all(5.0),
      )
    );
  }

  _showSnackBar(BuildContext context, FlashCardList item){

  }



}
2个回答

75
您可以使用 Future.wait 来等待多个 Future 完成。
body: FutureBuilder<List<FlashCardList>>(
    future: Future.wait([
        fetchFlashCardList(),
        fetchFlashCardListFromDB(),
    ]),

1
当我想使用它时,出现了这个错误:无法推断类型参数'E'。尝试为'E'推断'dynamic',但不起作用: 返回类型声明为'List<E>' 在需要'List<Future<_>>'的地方使用。 类型'dynamic'是从以下内容推断出来的: 参数'element'声明为'E' 但实参为'dynamic'。考虑向泛型传递显式类型参数。 - Benyamin
@Benyamin 假设你的 FutureBuilder 返回类型和 futures 列表中的类型不相关。错误明确说明了这一点。目前还没有解决你问题的现成方案。 - Konstantin Kozirev
你的意思是Flutter里还没有实现这个功能? - Benyamin
他们会同时执行吗? - faccio
不是真的。请参阅https://dart.dev/language/concurrency。 - Alexandre Ardhuin
@faccio 是的,我相信它们是并行执行的。 - Waqad Arshad

37

这是基于Alexandre的回答的一个示例(因为我发现自己在寻找如何处理结果):

FutureBuilder(
    future: Future.wait([
         firstFuture(), // Future<bool> firstFuture() async {...}
         secondFuture(),// Future<bool> secondFuture() async {...}
         //... More futures
    ]),
    builder: (
       context, 
       // List of booleans(results of all futures above)
       AsyncSnapshot<List<bool>> snapshot, 
    ){

       // Check hasData once for all futures.
       if (!snapshot.hasData) { 
          return CircularProgressIndicator();
       }

       // Access first Future's data:
       // snapshot.data[0]

       // Access second Future's data:
       // snapshot.data[1]

       return Container();

    }
);


2
问题在于 snapshot.data[0] 为空,导致出现错误 - 在 null 上调用 [0]。 - Abhishek
1
@Abhishek 你好,你是否在访问之前检查快照是否具有数据? - Jehad Nasser
是的,那就是问题所在,明白了,谢谢。希望你的答案能帮助许多人走过这条路。 - Abhishek
1
非常有用的答案和示例,谢谢! - Val
1
但是 "snapshot.hasData" 如何检测其是否具有数据,以及 firstFuture() 内部是什么? - NehaK
@NehaK 这是一个有点老的问题,但我刚刚遇到了。简单地对匹配您未来的数组元素执行空值检查,snapshot.data[x] == null。基本上就是这样。 - kenwen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接