I wrote FutureBuilder(future: api.fetchUsers(), ...) inside build. Every time the parent calls setState (even a text field), the request fires again and the spinner flashes. How do I load once?
DEV3 Developer Forum
Why does my FutureBuilder call the API again on every rebuild in Flutter?
Ask a question
Every post is reviewed first. Usually reviewed within 24 hours.
dev3 ·
0
1 reply
dev3 · Accepted answer
Cause
api.fetchUsers() is evaluated each time build runs, so a brand new Future is created, and FutureBuilder restarts when its future changes identity. build can run many times per second (setState, keyboard, theme, parent rebuild).
Fix: create the Future once in State
class UsersPage extends StatefulWidget {
const UsersPage({super.key});
@override
State<UsersPage> createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
late Future<List<User>> _future;
@override
void initState() {
super.initState();
_future = api.fetchUsers();
}
Future<void> _refresh() async {
setState(() => _future = api.fetchUsers());
await _future;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<User>>(
future: _future,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) return Text('Error: ${snap.error}');
final users = snap.data!;
return RefreshIndicator(
onRefresh: _refresh,
child: ListView(children: [for (final u in users) Text(u.name)]),
);
},
);
}
}
Better for real apps
Keep the data in a state holder instead of widget state: a Riverpod FutureProvider, a Bloc, or a ChangeNotifier. They cache the result across navigation and expose loading, error and data cleanly (see the pull to refresh list snippet).
Pitfalls
- Check
snap.hasErrorbeforesnap.data!, otherwise errors become null crashes. didUpdateWidgetis the place to reload if a constructor parameter (like a user id) changes.- Do not call
setStateafterdispose; guard withmountedin async callbacks.
0