Flutter:从 Future 获取价值的正确方法

Flutter: Correct approach to get value from Future

我有一个 returns 图像目录路径的函数,它会执行一些额外的检查,比如目录是否存在,然后它会相应地运行。

这是我的代码:

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 获取值时遇到问题。

案例场景: 我将数据存储在本地数据库中并尝试在列表视图中显示它。我正在使用 FutureBuilder,如 中所述。每个数据行都有一个图像与之连接(连接的意思是图像名称存储在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(),
                );
        }));

}

.then 内移动 return Scaffold(.....) 无效。因为 widget build returns 什么都没有。

我找到的另一个选项是 async/await 但最后还是出现了同样的问题,代码如下:

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

调用 _getImagesPath() returns Future,而不是实际数据。

我认为有很小的逻辑错误,但我自己找不到。

我看到您必须根据两个期货的输出来构建您的小部件。您可以使用两个 FutureBuilders 或使用辅助方法将它们组合成一个简化的代码单元。

此外,永远不要 compute/invoke 来自 build 函数的异步函数。它必须在之前初始化(在构造函数或 initState 方法中),否则小部件可能最终会永远重新绘制自己。

解决方案:为了简化代码,最好将两个未来的输出组合成一个 class,如下例所示:

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(),
              );
      },
    ),
  );
}

希望对您有所帮助。

将这段代码移到 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(),
                );
        }));
}

希望对您有所帮助!