FutureBuilder — What It Is, Working with Future in Flutter

Author: IT Sectr Published: 2026-07-02 Reading time: 8 min

FutureBuilder is a widget in Flutter that automatically rebuilds its interface based on the current state of AsyncSnapshot obtained from a provided Future. Unlike manually calling setState after await, FutureBuilder provides a declarative approach: it subscribes to the Future on first render and calls the builder function on every state change — loading, error, or ready data. According to the Flutter API Reference (2026), FutureBuilder is especially useful for loading data from the network, reading from a database, and any asynchronous operations where the UI needs to display a loading indicator, error message, or ready content.

Key Takeaways

  • FutureBuilder — Flutter widget for building UI based on Future state via AsyncSnapshot (none, waiting, active, done)
  • AsyncSnapshot — an object containing the current state of an async operation: connectionState, data, and error
  • builder — a callback function invoked on every Future state change to rebuild the UI
  • Error handling — AsyncSnapshot.hasError allows showing a fallback UI on async operation failure
  • ConnectionState — an enum with four values: none (no operation), waiting (awaiting), active (stream), done (completed)

What Is FutureBuilder in Flutter

FutureBuilder is a built-in Flutter widget from the widgets package that takes a Future and a builder function. When the state of the Future changes (running, completed with data, completed with error), FutureBuilder automatically rebuilds the UI by calling the builder with a new AsyncSnapshot. This eliminates the need to manually manage loading state via setState and flags.

Unlike StreamBuilder, which works with data streams (Stream), FutureBuilder is designed for one-shot asynchronous operations: HTTP request, file reading, database query. FutureBuilder manages the subscription to the Future itself: on first build, it starts the Future and tracks its completion. When the widget is destroyed, FutureBuilder does not cancel the Future — that is the developer’s responsibility.

According to the Flutter Cookbook (2026), FutureBuilder is recommended for cases where an async operation runs once at screen initialization. For recurring operations or data streams, use StreamBuilder. Both widgets follow the same Reactive UI pattern, but FutureBuilder is optimized for one-shot requests.

How FutureBuilder Works Under the Hood

The internal implementation of FutureBuilder subscribes to the Future using Future.then and catchError. When FutureBuilder starts, it sets connectionState to ConnectionState.waiting and calls the builder with empty data. On successful completion, connectionState changes to ConnectionState.done with data. On error, snapshot.error is filled with the error object. Each change triggers a widget rebuild.

AsyncSnapshot: States and Properties

AsyncSnapshot is a container object that FutureBuilder passes to the builder function on every state change. It contains all the information about the current status of the async operation: whether loading is in progress, what data was received, or whether an error occurred. Understanding AsyncSnapshot is key to properly building UI with FutureBuilder.

PropertyTypeDescription
connectionStateConnectionStateCurrent connection state (none, waiting, active, done)
dataT?Data received from the Future (null until completion or on error)
errorObject?Error object if the Future completed with an exception
hasDatabooltrue if data is not null and connectionState is ConnectionState.done
hasErrorbooltrue if the Future completed with an error

ConnectionState: Four States of an Async Operation

The ConnectionState enum defines the stage of an async operation. None — initial state when the Future has not yet been started (rarely used, typically on first build without initialData). Waiting — the Future is running, data not yet received. Active — used only by StreamBuilder for streams with partial data. Done — the Future has completed, data is available via snapshot.data or error via snapshot.error.

Proper handling of all AsyncSnapshot states in the builder function is a mandatory requirement for production code. If you do not handle the waiting state, the user will see an empty screen during loading. If you do not handle hasError, the user will get an Exception without explanation. The recommended pattern: check hasError → check hasData → show loading by default.

FutureBuilder Usage Patterns

FutureBuilder can be used in several standard patterns, each solving a specific task. Let us look at the main scenarios: loading data on initialization, loading with caching, parallel requests, and error handling with retry.

Loading Data on Screen Initialization

The most common pattern — FutureBuilder in the build method of a StatefulWidget or StatelessWidget. The Future is passed from initState or created directly in build. It is important not to create the Future in the build method on every rebuild — this will lead to repeated requests. Use a Future stored in a State field.

Loading with Caching and Refresh

To prevent repeated requests, FutureBuilder can be combined with CachedNetworkImage or a local cache. After the first load, the data is saved in memory or SharedPreferences, and FutureBuilder displays cached data instantly while refreshing from the network in parallel. This improves UX through instant response.

According to pub.dev (2026), caching is especially relevant for images and data lists. FutureBuilder with CachedNetworkImageProvider automatically displays a cached image, and when it is absent — a loading indicator followed by the downloaded file.

FutureBuilder vs setState: Which to Choose

FutureBuilder and manual state management via setState are two approaches to async UI in Flutter. Each has its own advantages and limitations. The choice depends on the screen complexity and the number of async operations.

FutureBuilder wins in simplicity: you do not need to declare fields for loading state, data, and error — everything is managed through AsyncSnapshot. It is ideal for simple screens with one async operation (one HTTP request, database read). However, with 5+ async operations on one screen, FutureBuilder creates excessive nesting — resulting in a “pyramid” of nested FutureBuilders.

setState with manual state flags gives more control and readability for complex logic. For screens with multiple dependent requests (load user → load their orders → load order details), it is better to use setState with ChangeNotifier or Bloc. According to the Flutter State Management Guide (2026), for complex scenarios Riverpod or Bloc is recommended over FutureBuilder, as they provide better separation of logic and presentation.

FutureBuilder Example with Network Data Loading

Let us consider a practical FutureBuilder example for loading a list of users from a REST API. The code demonstrates correct handling of all three AsyncSnapshot states: loading, error, and ready data.

dart
class UserListPage extends StatefulWidget {
  const UserListPage({super.key});

  @override
  State<UserListPage> createState() => _UserListPageState();
}

class _UserListPageState extends State<UserListPage> {
  final Future<List<User>> usersFuture = UserRepository().fetchUsers();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Users')),
      body: FutureBuilder<List<User>>(
        future: usersFuture,
        builder: (context, AsyncSnapshot<List<User>> snapshot) {
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.error_outline, size: 48, color: Colors.red),
                  const SizedBox(height: 16),
                  Text('Error: ${snapshot.error}'),
                ],
              ),
            );
          }

          if (snapshot.hasData) {
            final users = snapshot.data!;
            return ListView.builder(
              itemCount: users.length,
              itemBuilder: (context, index) {
                return ListTile(
                  leading: CircleAvatar(backgroundImage: NetworkImage(users[index].avatarUrl)),
                  title: Text(users[index].name),
                  subtitle: Text(users[index].email),
                );
              },
            );
          }

          return const Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

In the example, FutureBuilder handles all three states. On error, an icon with an error message is displayed. On successful load — a ListView with avatars and names. During loading — a CircularProgressIndicator. The Future is declared as a class field, which prevents repeated invocation on rebuild. This pattern covers 90% of FutureBuilder usage scenarios in mobile apps.

Frequently Asked Questions

Why does FutureBuilder call builder multiple times?

FutureBuilder calls the builder on every Future state change: the first time on creation (connectionState: none or waiting), the second time on completion (connectionState: done). If the parent widget rebuilds, FutureBuilder also rebuilds. To prevent repeated calls, make sure the Future is created outside the build method — otherwise every build call creates a new Future.

How to prevent a repeated request on rebuild?

Store the Future in a StatefulWidget field (in initState) or use memoization. If the Future is created inside the build method, every build call will create a new Future, and FutureBuilder will restart the async operation. For StatelessWidget, use the cached_future package or keep-alive widgets so the Future runs once regardless of rebuilds.

How is FutureBuilder different from StreamBuilder?

FutureBuilder is designed for one-shot async operations (one HTTP request, one database read). StreamBuilder works with data streams that can emit multiple values over time (chat, price updates, geolocation). StreamBuilder supports ConnectionState.active for partial data, while FutureBuilder only supports waiting and done.

How to use FutureBuilder with multiple Futures?

For multiple parallel Futures, use Future.wait and pass the result to a single FutureBuilder. Future.wait takes a list of Futures and returns a Future — when all Futures complete, the builder receives an array of results. For sequential requests, use a Future.then chain inside one Future or nested FutureBuilders (less readable). An alternative is the riverpod package with AsyncValue for multiple async states.

How to cancel a Future when leaving the screen?

FutureBuilder does not cancel the Future automatically. To cancel, use CancelableOperation from the async package or a custom mechanism via a cancelled flag in State. Set the flag in dispose(), and check it after the Future completes before calling setState. Alternatively, use the riverpod package with AutoDispose, which automatically cancels async operations when leaving the screen.

Summary

  • FutureBuilder — Flutter widget for declarative UI building based on Future state via AsyncSnapshot (waiting, done, error)
  • AsyncSnapshot — container with connectionState, data, and error; essential for correct handling of all async operation states
  • builder — callback with three branches: hasError (show error), hasData (display data), default (loading indicator)
  • FutureBuilder vs setState — FutureBuilder is simpler for one operation, setState with Bloc/Riverpod is better for complex logic with multiple requests
  • Preventing repeated requests — Future should be a State field, do not create it in the build method to avoid restart on every rebuild
  • Canceling a Future — FutureBuilder does not cancel the Future on dispose; use CancelableOperation or a cancel flag to prevent setState after destruction
  • Multiple Futures — for parallel requests use Future.wait with a single FutureBuilder; for sequential ones — chains in a single Future

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also