Как загрузить список только после того, как FutureBuilder получит snapshot.data?

У меня есть метод JSON, который возвращает 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 list сначала на экране, а через несколько секунд он загрузит список.

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

Это мое FutureBuilder что называется в build(), однако, если я предоставлю список приветствия перед оператором возврата, он покажет мне, что метод 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();
              
            });

2 ответа

Вы вызываете свой метод getData несколько раз. Не делай этого. Ваш пользовательский интерфейс ждет одного вызова, а ваш код - другого. Это беспорядок. Позвоните один раз и дождитесь этого звонка.

Вам нужно определить будущее, которого нужно ждать в вашем состоянии:

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, установить переменную Future в своем состоянии, затем вы можете вызвать функцию .then() внутри функции addPostFrameCallback(), чтобы сохранить результат, и, наконец, используя переменную будущего в ваш FutureBuilder, как это.

      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 и загрузить их только один раз.

В моем случае мне нужно было получить карту объектов из моего будущего, затем пользователь мог выбрать некоторые из этих объектов, сохранить их на карте и выполнить некоторые вычисления на основе этого выбора. Но при выборе этих данных с именем setState() для обновления пользовательского интерфейса я не смог сделать это без необходимости писать сложную логику для правильного сохранения выбора пользователя на карте и необходимости снова вызывать будущее за пределами объем FutureBuilder, чтобы получить данные для других моих расчетов.

В приведенном выше примере вы можете загрузить свое будущее только один раз и использовать данные моментального снимка вне области действия FutureBuilder без необходимости повторного вызова будущего.

Я надеюсь, что я был достаточно ясен с этим примером. Если у вас есть какие-либо сомнения, я с радостью их проясню.

Другие вопросы по тегам