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

Posted

技术标签:

【中文标题】Flutter:从 Future 中获取价值的正确方法【英文标题】:Flutter: Correct approach to get value from Future 【发布时间】:2020-01-22 02:37:10 【问题描述】:

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

这是我的代码:

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

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

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

_getImagesPath() async 
    return await imgPath();

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

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

【问题讨论】:

你的问题是什么?我在你的代码中没有看到任何FutureBuilder...所以你是在使用它还是StatefulWidget 例如? 我正在使用StatefulWidget,我已经分享了完整的代码,如果您需要更多信息,请告诉我。 不,在您现在发布的代码中,您使用的是FutureBuilder,所以我的问题是:它是正确的代码吗?或者你正在使用StatefulWidget 我看到你知道如何在代码中使用FutureBuilder,并且你也知道将_getImagesPath设为异步函数。将这两者结合起来有什么错误吗? @pskink 正如我在第一条评论中提到的,我使用的是StatefulWidgetWidget build 方法在 StatefulWidget 类中。 【参考方案1】:

我看到您必须从两个期货的输出中构建您的小部件。您可以使用两个 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(),
  );

现在把所有东西放在 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(),
              );
      ,
    ),
  );


希望对你有帮助。

【讨论】:

感谢您的回答,使用您的解决方案解决了路径问题。但由于某些原因,资产仍未显示。控制台unable to load asset: complete_path_of_asset 出现错误,路径100% 正确且资产也存在。为了测试,我从调试控制台复制了一条路径并在Image.asset 中输入,它运行良好。这怎么可能? 确保路径中没有双斜杠,这可能是个问题 我已经仔细检查过,没有任何问题;包括双斜线。路径是正确的,因为我已经从控制台复制了相同的路径并在 Image.asset 内部使用,它工作正常。 我注意到一件奇怪的事情,如果我运行应用程序一次并导航到该屏幕。之后,如果我从 IDE 中单击 Restart 按钮,它会重新打开应用程序并进入登录屏幕。当我登录并在那里导航时,图像工作正常。代码没有任何变化,在重新启动期间,我尝试了几次,它可以工作。可能油漆有问题或者需要async/await 来自控制台&lt;asynchronous suspension&gt;Image provider: AssetImage(bundle: null, name: "/data/user/0/com.alena.data_handler/app_flutter/images/1.jpg")的错误行【参考方案2】:

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

希望对你有帮助!

【讨论】:

感谢您的回答,它在第一次加载时不起作用。但是当我们回去时它会起作用,然后再回来,非常奇怪的问题。 @Atlas 坚持 OP 的结构。如果有人问如何修车,不建议买新车。

以上是关于Flutter:从 Future 中获取价值的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法从 Flutter 中的 Future 函数获取反馈

Flutter - 如何在我的未来构建器文本小部件中获得价值回报

在 Flutter 的流构建器中使用 Future 构建器是正确的

Flutter 如何从自定义小部件中获取价值

如何在 Flutter 中返回 Future<Response>?

Flutter 中的 Stream 和 Future