如何将FutureBuilder
与setState
一起正确使用?例如,当我创建一个有状态的小部件时,它开始加载数据(FutureBuilder),然后我应该用新数据更新列表,因此我使用setState,但是它开始循环到无限(因为我再次重建了小部件),任何解决方案?
class FeedListState extends State<FeedList> {
Future<Null> updateList() async {
await widget.feeds.update();
setState(() {
widget.items = widget.feeds.getList();
});
//widget.items = widget.feeds.getList();
}
@override
Widget build(BuildContext context) {
return new FutureBuilder<Null>(
future: updateList(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Scrollbar(
child: new RefreshIndicator(
child: ListView.builder(
physics:
const AlwaysScrollableScrollPhysics(), //Even if zero elements to update scroll
itemCount: widget.items.length,
itemBuilder: (context, index) {
return FeedListItem(widget.items[index]);
},
),
onRefresh: updateList,
),
);
}
},
);
}
}
答案 0 :(得分:3)
实际上,它会无限循环,因为每当调用build
时,也会调用updateList
并返回全新的未来。
您必须保持build
的纯正。它应该只读取并组合变量和属性,但绝不会引起任何副作用!
另一个注意事项:StatefulWidget
子类的所有字段都必须是最终字段(widget.items = ...
不好)。更改的状态必须存储在State
对象中。
在这种情况下,您可以在将来自己存储结果(列表数据),而无需单独的字段。从将来调用setState
甚至很危险,因为将来可能会在处置状态之后完成,并且会引发错误。
以下是一些考虑到所有这些问题的更新代码:
class FeedListState extends State<FeedList> {
// no idea how you named your data class...
Future<List<ItemData>> _listFuture;
@override
void initState() {
super.initState();
// initial load
_listFuture = updateAndGetList();
}
void refreshList() {
// reload
setState(() {
_listFuture = updateAndGetList();
});
}
Future<List<ItemData>> updateAndGetList() async {
await widget.feeds.update();
// return the list here
return widget.feeds.getList();
}
@override
Widget build(BuildContext context) {
return new FutureBuilder<List<ItemData>>(
future: _listFuture,
builder: (BuildContext context, AsyncSnapshot<List<ItemData>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return new Center(
child: new CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
final items = snapshot.data ?? <ItemData>[]; // handle the case that data is null
return new Scrollbar(
child: new RefreshIndicator(
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(), //Even if zero elements to update scroll
itemCount: items.length,
itemBuilder: (context, index) {
return FeedListItem(items[index]);
},
),
onRefresh: refreshList,
),
);
}
},
);
}
}
答案 1 :(得分:0)
可以使用SchedulerBinding在Future Builders或Stream Builders中使用 setState(),
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
答案 2 :(得分:0)
使用 setState
时不需要 FutureBuilder
。
class MyPage extends StatefulWidget {
@override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
// Declare a variable.
late final Future<int> _future;
@override
void initState() {
super.initState();
_future = _calculate(); // Assign your Future to it.
}
// This is your actual Future.
Future<int> _calculate() => Future.delayed(Duration(seconds: 3), () => 42);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<int>(
future: _future, // Use your variable here (not the actual Future)
builder: (_, snapshot) {
if (snapshot.hasData) return Text('Value = ${snapshot.data!}');
return Text('Loading...');
},
),
);
}
}