Reputation: 385
How to use the FutureBuilder
with setState
properly? For example, when i create a stateful widget its starting to load data (FutureBuilder) and then i should update the list with new data, so i use setState, but its starting to loop for infinity (because i rebuild the widget again), any solutions?
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,
),
);
}
},
);
}
}
Upvotes: 38
Views: 48095
Reputation: 268504
You don't need setState
while using 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...');
},
),
);
}
}
Upvotes: 2
Reputation: 4027
Use can SchedulerBinding for using setState() inside Future Builders or Stream Builder,
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
Upvotes: 10
Reputation: 30123
Indeed, it will loop into infinity because whenever build
is called, updateList
is also called and returns a brand new future.
You have to keep your build
pure. It should just read and combine variables and properties, but never cause any side effects!
Another note: All fields of your StatefulWidget
subclass must be final (widget.items = ...
is bad). The state that changes must be stored in the State
object.
In this case you can store the result (the data for the list) in the future itself, there is no need for a separate field. It's even dangerous to call setState
from a future, because the future might complete after the disposal of the state, and it will throw an error.
Here is some update code that takes into account all of these things:
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,
),
);
}
},
);
}
}
Upvotes: 65