DEV3 Technologies home

DEV3 Developer Forum

Why does my FutureBuilder call the API again on every rebuild in Flutter?

Flutter helpSolved0 views
dev3 ·

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?

#flutter#futurebuilder#async

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.hasError before snap.data!, otherwise errors become null crashes.
  • didUpdateWidget is the place to reload if a constructor parameter (like a user id) changes.
  • Do not call setState after dispose; guard with mounted in async callbacks.
0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.