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 |
|
| TimeoutException | Future takes longer than expected | Set timeout using withTimeout |
|
| NetworkError | Failure due to network issues | Check for InternetException or similar |
|
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 futurecomputation: 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
Durationobject.
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. | |
| async/await | Provides a cleaner syntax for chaining futures using await. |
|
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. |
|
| 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
Futurethat resolves with a list of results. - Use
FutureBuilderto 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.waitingto show loading indicator - Handle
snapshot.hasErrorfor error states - Display actual data when
snapshot.hasDatais 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.