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

Ale*_*ena 8 asynchronous future async-await dart flutter

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

这是我的代码:

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;

}
Run Code Online (Sandbox Code Playgroud)

这段代码按预期工作,但我在从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(),
                );
        }));
Run Code Online (Sandbox Code Playgroud)

}

return Scaffold(.....)内部转移.then不起作用。因为小部件构建不返回任何内容。

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

_getImagesPath() async {
    return await imgPath();
}
Run Code Online (Sandbox Code Playgroud)

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

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

Che*_*ddy 10

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

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

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

build方法所需数据:


class DataRequiredForBuild {
  String imagesPath;
  List items;

  DataRequiredForBuild({
    this.imagesPath,
    this.items,
  });
}
Run Code Online (Sandbox Code Playgroud)

获取所有必需数据的函数:


Future<DataRequiredForBuild> _fetchAllData() async {
  return DataRequiredForBuild(
    imagesPath: await getImagesPath(),
    items: await databaseHelperGetList(),
  );
}
Run Code Online (Sandbox Code Playgroud)

现在将所有内容放在 Widget 中:

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

Run Code Online (Sandbox Code Playgroud)

希望能帮助到你。


Atl*_*dal 6

将这段代码移入内部FutureBuilder应该可以解决问题。

getImagesPath().then((path){
 imagesPath = path;
 print(imagesPath); //prints correct path
});
Run Code Online (Sandbox Code Playgroud)

所以你的最终代码应该如下所示:

@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(),
                );
        }));
}
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!