如何在 FutureBuilder 获取 snapshot.data 后才加载列表?

How to load list only after FutureBuilder gets snapshot.data?

我有一个JSON方法,完成后return是List

Future<List<Feed>> getData() async {
  List<Feed> list;
  String link =
      "https://example.com/json";
  var res = await http.get(link);
  if (res.statusCode == 200) {
    var data = json.decode(res.body);
    var rest = data["feed"] as List;
    list = rest.map<Feed>((json) => Feed.fromJson(json)).toList();
  }
  return list;
}

然后我调用它,在我的 initState() 中包含一个列表 hello,它将过滤掉 JSON 列表,但它首先在屏幕上显示 null 列表几秒钟后加载列表。

    getData().then((usersFromServer) { 
          setState(() {.   //Rebuild once it fetches the data
hello = usersFromServer
          .where((u) => (u.category.userJSON
              .toLowerCase()
              .contains('hello'.toLowerCase())))
          .toList();
            users = usersFromServer;
            filteredUsers = users;
          });
        });

这是我在 build() 方法中调用的 FutureBuilder,但是如果我在 return 语句之前提供 hello 列表,它会告诉我方法 where() 在 null 上被调用(我用来过滤掉 hello() 的列表方法)

FutureBuilder(
            future: getData(),
            builder: (context, snapshot) {
              return snapshot.data != null ?
                Stack(
                  children: <Widget>[
                    CustomScrollView(slivers: <Widget>[
                      SliverGrid(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200.0,
    mainAxisSpacing: 10.0,
    crossAxisSpacing: 10.0,
    childAspectRatio: 4.0,
  ),
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return Container(
        child: puzzle[0],
      );
    },
    childCount: 1,
  ),
)
                    ]),
                  ],
                )
               :
               CircularProgressIndicator();
              
            });

您多次调用 getData 方法。不要那样做。您的 UI 等待一个电话,您的代码等待另一个电话。那是一团糟。调用它 一次 并等待 那个 调用。

您需要定义在您的状态下等待的未来:

Future<void> dataRetrievalAndFiltering;

然后在你的 initstate 中,将整个操作分配给这个未来:

(注意我完全删除了setState,这里不再需要了)

dataRetrievalAndFiltering = getData().then((usersFromServer) { 
      hello = usersFromServer.where((u) => (u.category.userJSON.toLowerCase().contains('hello'.toLowerCase()))).toList();
        users = usersFromServer;
        filteredUsers = users;
      
    });

现在您的FurtureBuilder实际上可以等待那个特定的未来,而不是等待您通过再次调用您的方法生成的新未来:

FutureBuilder(
        future: dataRetrievalAndFiltering,
        builder: (context, snapshot) {

现在你有一个未来,你可以等待。

此回复有点晚了,无法为您提供帮助,但对于任何想知道如何加载 Future 并使用它的任何数据来设置其他变量而不会遇到将其保存在变量中然后调用 setState 的问题的人() 再次加载你的未来,你可以,正如@nvoigt所说,在你的状态中设置一个未来的变量,然后你可以调用 addPostFrameCallback() 函数中的 .then() 函数来保存结果,最后像这样在 FutureBuilder 中使用 future 变量。

class _YourWidgetState extends State<YourWidget> {
  ...
  late MyFutureObject yourVariable;
  late Future<MyFutureObject> _yourFuture;

  @override
  void initState() {
    super.initState();
    _yourFuture = Database().yourFuture();

    WidgetsBinding.instance?.addPostFrameCallback((_) {
      _yourFuture.then((result) {
        yourVariable = result;
        // Execute anything else you need to with your variable data.
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _yourFuture,
      builder: ...
    );
  }
}

dynamic doSomeStuffWithMyVariable() {
  // Do some stuff with `yourVariable`
}

所以,这样做的好处是可以在FutureBuilder的范围之外使用未来加载的数据,而且只加载一次。

在我的例子中,我需要从我的未来获取对象地图,然后用户可以select其中一些对象,将它们保存在地图上并根据该地图进行一些计算select离子。但是当 selecting 调用 setState() 的数据以更新 UI 时,我无法在不编写复杂逻辑来保存用户的情况下这样做 selection正确地在地图中,并且必须在 FutureBuilder 的范围之外再次调用 future 以获取它的数据以用于我的其他计算。

通过上面的示例,您可以只加载一次您的未来,并在 FutureBuilder 范围之外使用快照数据,而无需再次调用未来。

我希望我对这个例子足够清楚。如果您有任何疑问,我很乐意澄清。