Flutter:从未来获得价值的正确方法

时间:2019-09-21 08:18:53

标签: asynchronous flutter dart async-await future

我有一个返回图像目录路径的函数,它执行一些其他检查,例如目录是否存在,然后行为相应。

这是我的代码:

Future<String> getImagesPath() async {
   final Directory appDir = await getApplicationDocumentsDirectory();
   final String appDirPath = appDir.path;

   final String imgPath = appDirPath + '/data/images';

   final imgDir = new Directory(imgPath);

   bool dirExists = await imgDir.exists();

   if (!dirExists) {
        await new Directory(imgPath).create(recursive: true);
   }

   return imgPath;

}

这段代码可以正常工作,但是在从Future获取价值方面遇到了问题。

案例: 我将数据存储在本地数据库中,并试图在listview中显示它。我正在使用FutureBuilder,如本answer所述。每个数据行都有一个与之相连的映像(连接表示映像名称存储在db中)。

Widget build方法中,我有以下代码:

@override
Widget build(BuildContext context) {
 getImagesPath().then((path){
     imagesPath = path;
     print(imagesPath); //prints correct path
   });
 print(imagesPath);   //prints null

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.asset(image),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));

}

return Scaffold(.....)内移动.then无效。因为小部件构建不返回任何内容。

我找到的另一个选项是async/await,但最后,同样的问题,下面的代码可用:

_getImagesPath() async {
    return await imgPath();
}

调用_getImagesPath()会返回Future,而不是实际数据。

我相信这是一个很小的逻辑错误,但我自己却找不到。

2 个答案:

答案 0 :(得分:1)

我看到您必须从两个Future的输出中构建窗口小部件。您可以使用两个FutureBuilders或使用辅助方法将它们组合为一个并简化代码。

也不要从build函数中计算/调用异步函数。必须先对其进行初始化(在构造函数或initState方法中),否则其他明智的小部件可能永远都无法重新绘制。

为了解决方案,简化代码,最好将两个将来的输出合并到一个类中,如下所示:

build方法所需的数据:


class DataRequiredForBuild {
  String imagesPath;
  List items;

  DataRequiredForBuild({
    this.imagesPath,
    this.items,
  });
}

获取所有必需数据的功能:


Future<DataRequiredForBuild> _fetchAllData() async {
  return DataRequiredForBuild(
    imagesPath: await getImagesPath(),
    items: await databaseHelperGetList(),
  );
}

现在将所有内容放到小部件中:

Future<DataRequiredForBuild> _dataRequiredForBuild;

@override
void initState() {
  super.initState();
  // this should not be done in build method
  _dataRequiredForBuild = _fetchAllData();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    //removed
    body: FutureBuilder<DataRequiredForBuild>(
      future: _dataRequiredForBuild,
      builder: (context, snapshot) {
        return snapshot.hasData
            ? ListView.builder(
                itemCount: snapshot.data.items.length,
                itemBuilder: (_, int position) {
                  final item = snapshot.data.items[position];
                  final image = "${snapshot.data.imagesPath}/${item.row[0]}.jpg";
                  return Card(
                      child: ListTile(
                    leading: Image.asset(image),
                    title: Text(item.row[1]),
                    subtitle: Text(item.row[2]),
                    trailing: Icon(Icons.launch),
                  ));
                })
            : Center(
                child: CircularProgressIndicator(),
              );
      },
    ),
  );
}

希望有帮助。

答案 1 :(得分:1)

将这段代码移入FutureBuilder可以解决此问题。

getImagesPath().then((path){
 imagesPath = path;
 print(imagesPath); //prints correct path
});

因此您的最终代码应如下所示:

@override
Widget build(BuildContext context) {

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    getImagesPath().then((path){
                        imagesPath = path;
                    });

                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.file(File(image)),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));
}

希望有帮助!