Skip to main content

Using FutureBuilder

1. Understanding Future Class

Property/Method Description Notes
Future<T> Represents a computation that will complete at some point in the future. Generic type T defines the return type of the asynchronous operation.
Future.delayed() Creates a Future that completes after a specified duration. Useful for simulating network delays or scheduling tasks.
Future.wait() Waits for all Futures in a list to complete. Can be used to run multiple asynchronous operations concurrently.
Future.any() Waits for any of the provided Futures to complete. Useful when you only need one of several asynchronous operations to finish.
Future.microtask() Creates a Future that runs in the microtask queue. Used for running code after the current event loop iteration but before the next one.

Example: Creating a Future

Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () => "Data fetched");
}

void main() {
  fetchData().then((data) {
    print(data);
  });
}
Note: Futures are used to handle asynchronous operations in Dart, which is the language Flutter is built on.

2. Using FutureBuilder Widget

Property Values Description
future Future<T> The asynchronous operation to await
builder Widget Function(BuildContext, AsyncSnapshot<T>) Function that builds the widget based on snapshot state
initialData T? Data to display before future completes

Example: Basic FutureBuilder Usage

Future<String> fetchUserData() async {
  await Future.delayed(Duration(seconds: 2));
  return "John Doe";
}

@override
Widget build(BuildContext context) {
  return FutureBuilder<String>(
    future: fetchUserData(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return CircularProgressIndicator();
      } else if (snapshot.hasError) {
        return Text('Error: ${snapshot.error}');
      } else if (snapshot.hasData) {
        return Text('Welcome, ${snapshot.data}!');
      } else {
        return Text('No data');
      }
    },
  );
}
Note: The FutureBuilder widget rebuilds whenever the future completes or when new data is available.

Key Points

  • State Management: Handles asynchronous data loading and UI updates
  • Connection States: Provides ConnectionState.waiting, ConnectionState.done, etc.
  • Snapshot: Contains data, error, and connection state

3. Understanding ConnectionState

Connection State Description Typical Use Case
none The future has not started executing yet. Display a loading indicator or placeholder content before data is fetched.
waiting The future is currently executing and has not completed yet. Render a spinner or progress bar while waiting for data to arrive.
done The future has completed successfully and returned a value. Display the actual data or UI built from the fetched data.
error The future completed with an error. Show an error message or fallback UI when data fetching fails.

Example: Handling Different Connection States

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return "Sample Data";
}

Widget build(BuildContext context) {
  return FutureBuilder<String>(
    future: fetchData(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.none) {
        return Text('Not started');
      } else if (snapshot.connectionState == ConnectionState.waiting) {
        return CircularProgressIndicator();
      } else if (snapshot.connectionState == ConnectionState.done) {
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        } else {
          return Text(snapshot.data!);
        }
      } else {
        return Text('Unexpected state');
      }
    },
  );
}

4. Handling Future Data

Property Description Usage
future The asynchronous operation to be awaited Future<String> fetchUserData() async { ... }
builder Function that builds the UI based on the Future's state (BuildContext context, AsyncSnapshot<String> snapshot) { ... }
initialData Data to display before the Future completes initialData: 'Loading...'

Example: Basic FutureBuilder Usage

FutureBuilder<String>(
  future: fetchUserData(),
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else if (snapshot.hasData) {
      return Text('User: ${snapshot.data}');
    } else {
      return Text('No data');
    }
  },
)
Note: The AsyncSnapshot provides access to the state of the Future, including connection state and data.

Handling Data States

  • waiting: Display loading indicator
  • hasError: Show error message
  • hasData: Render data
  • noData: Show default or empty state

5. Handling Future Errors

Error Type Description Handling Strategy Example
Error Generic error thrown during future execution Use catchError in FutureBuilder
FutureBuilder<String>(
  future: fetchData(),
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    // ...
  },
)
TimeoutException Future takes longer than expected Set timeout using withTimeout
Future<String> future = Future.delayed(Duration(seconds: 5), () => 'data');
FutureBuilder<String>(
  future: future.timeout(Duration(seconds: 2)),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    }
    if (snapshot.hasError && snapshot.error is TimeoutException) {
      return Text('Request timed out');
    }
    // ...
  },
)
NetworkError Failure due to network issues Check for InternetException or similar
FutureBuilder<String>(
  future: http.get(Uri.parse('https://api.example.com/data')),
  builder: (context, snapshot) {
    if (snapshot.hasError && snapshot.error is SocketException) {
      return Text('Network error occurred');
    }
    // ...
  },
)

Example: Error Handling in FutureBuilder

class ErrorHandlingExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchData(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        if (snapshot.hasData) {
          return Text(snapshot.data!);
        }
        return Text('No data');
      },
    );
  }
}
Note: Always handle snapshot.hasError to provide a graceful user experience when futures fail.

6. Using Future.delayed() Method

Method Description Parameters Return Type
Future.delayed() Creates a Future that completes after a specified duration. duration: Duration to wait before completing the future
computation: Optional computation to run after delay
Future<T>

Example: Delayed Future Execution

import 'dart:async';

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return "Data fetched after 2 seconds";
}

// Usage
void main() async {
  print("Starting...");
  String data = await fetchData();
  print(data);
}
Note: Future.delayed() is commonly used to simulate network delays or introduce artificial delays in UI testing.

Key Points

  • Future.delayed() is a utility function to delay execution of a Future.
  • It's useful for simulating async operations or introducing delays in UIs.
  • The delay is specified using a Duration object.

7. Chaining Futures

Chaining futures allows you to execute a sequence of asynchronous operations where each operation depends on the result of the previous one. This is commonly done using the then method or async/await syntax.

Method Description Example
then() Used to chain asynchronous operations. The next operation starts after the previous one completes.
Future<String> fetchUserData() {
  return Future.delayed(Duration(seconds: 1), () => "User Data");
}

Future<String> processUserData(String data) {
  return Future.delayed(Duration(seconds: 1), () => "Processed: $data");
}

// Chaining with then()
fetchUserData().then(processUserData).then((result) {
  print(result); // Output: Processed: User Data
});
async/await Provides a cleaner syntax for chaining futures using await.
Future<String> fetchUserData() async {
  await Future.delayed(Duration(seconds: 1));
  return "User Data";
}

Future<String> processUserData(String data) async {
  await Future.delayed(Duration(seconds: 1));
  return "Processed: $data";
}

// Chaining with async/await
Future<void> chainFutures() async {
  String userData = await fetchUserData();
  String processedData = await processUserData(userData);
  print(processedData); // Output: Processed: User Data
}

Example: Chaining Futures in a UI

This example demonstrates how to chain futures inside a FutureBuilder to display data sequentially.

class ChainedFutureBuilder extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchUserData().then(processUserData),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        return Text(snapshot.data ?? 'No data');
      },
    );
  }
}

8. Using Future.wait() for Multiple Futures

Future.wait() is a utility function that allows you to wait for multiple Future objects to complete. It returns a single Future that completes when all the provided futures have completed. This is particularly useful in Flutter when you need to wait for multiple asynchronous operations before updating the UI.

Property Description Example
futures A list of Future objects to wait for.
[
  fetchUserData(),
  fetchUserPreferences(),
  fetchUserSettings()
]
onError An optional callback to handle errors from any of the futures. (error, stackTrace) => print('Error: $error')

Example: Using Future.wait() with FutureBuilder

Future<List> fetchMultipleData() {
  return Future.wait([
    fetchUserData(),
    fetchUserPreferences(),
    fetchUserSettings()
  ]);
}

Widget build(BuildContext context) {
  return FutureBuilder<List>(
    future: fetchMultipleData(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return CircularProgressIndicator();
      }

      if (snapshot.hasError) {
        return Text('Error: ${snapshot.error}');
      }

      if (snapshot.hasData) {
        final userData = snapshot.data![0];
        final preferences = snapshot.data![1];
        final settings = snapshot.data![2];

        return Text('User: $userData, Preferences: $preferences, Settings: $settings');
      }

      return Container();
    },
  );
}
Note: Future.wait() completes when all futures in the list have completed. If any of the futures fail, Future.wait() will also fail.
Warning: If any of the futures in the list fail, Future.wait() will propagate the error. Make sure to handle errors appropriately using onError or by wrapping futures in try-catch blocks.

Key Points

  • Future.wait() waits for all futures to complete.
  • It returns a single Future that resolves with a list of results.
  • Use FutureBuilder to display UI based on the results.
  • Errors in any future will cause Future.wait() to fail.

9. Implementing Loading States

Example: Basic Loading State

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return "Data loaded";
}

class LoadingExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchData(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Center(child: Text("Error: ${snapshot.error}"));
        }
        return Center(child: Text(snapshot.data!));
      },
    );
  }
}
State Description UI Representation
waiting Initial state before data is fetched ProgressIndicator
done Data has been successfully fetched Text(snapshot.data!)
error An error occurred during data fetching Text("Error: ...")
Note: Use ProgressIndicator or custom loading widgets for better UX.
Warning: Always handle snapshot.hasError to prevent crashes.

Loading State Management

  • Use ConnectionState.waiting to show loading indicator
  • Handle snapshot.hasError for error states
  • Display actual data when snapshot.hasData is true

10. Avoiding FutureBuilder Rebuilds

Example: Using const FutureBuilder

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchData(),
      const: true, // Prevents unnecessary rebuilds
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!);
        }
        return CircularProgressIndicator();
      },
    );
  }
}
Property Values Description
const true When true, the FutureBuilder will not rebuild when the same future is passed
Note: Setting const: true ensures that the FutureBuilder does not rebuild unnecessarily when the same future is provided, improving performance.

Was this article helpful?