Как загрузить список только после того, как 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 без необходимости повторного вызова будущего.
Я надеюсь, что я был достаточно ясен с этим примером. Если у вас есть какие-либо сомнения, я с радостью их проясню.