Introduction

Flutter provides various widgets to handle asynchronous operations, and FutureBuilder is one of the most powerful ones. It allows you to build UI dynamically based on the state of a Future. This tutorial will guide you through the fundamentals, best practices, and real-world examples of using FutureBuilder in Flutter.

What is FutureBuilder?

FutureBuilder is a Flutter widget that rebuilds itself based on the latest snapshot of an asynchronous operation. It is commonly used for API calls, database queries, or other asynchronous computations.

FutureBuilder Syntax

FutureBuilder<T>(
  future: someFutureFunction(),
  builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
    // Handle different states here
  },
)

Parameters

  • future: The Future that will be executed.
  • builder: A function that returns a widget based on the AsyncSnapshot of the Future.

Handling Different States in FutureBuilder

The AsyncSnapshot provides different states to handle:

  1. ConnectionState.waiting → Display a loading indicator.
  2. snapshot.hasError → Handle errors properly.
  3. snapshot.hasData → Display fetched data.
  4. Else case → Handle empty or null data.

Example: FutureBuilder with Simulated Delay

import 'package:flutter/material.dart';
import 'dart:async';
class FutureBuilderExample extends StatelessWidget {
  Future<String> fetchData() async {
    await Future.delayed(Duration(seconds: 3)); // Simulate network delay
    return "Hello, FutureBuilder!";
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("FutureBuilder Example")),
      body: Center(
        child: FutureBuilder<String>(
          future: fetchData(),
          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(snapshot.data!, style: TextStyle(fontSize: 20));
            } else {
              return Text("No Data Available");
            }
          },
        ),
      ),
    );
  }
}

Best Practices for Using FutureBuilder

✅ Do's

Pass a Future to FutureBuilder, don't call the function inside builder. ✔ Handle all states properly – always check for loading, error, and data states. ✔ Use caching or state management to avoid redundant API calls. ✔ Use async/await for cleaner code instead of deeply nesting Future chains.

❌ Don'ts

❌ Avoid calling the future inside builder, as it will cause unnecessary rebuilds.

FutureBuilder(
  future: fetchData(), // ✅ Correct
  builder: ...
)

Instead of:

FutureBuilder(
  future: fetchData(), // ❌ Wrong - called inside builder
  builder: (context, snapshot) {
    fetchData();
    return ...;
  },
)

Fetching Data from an API using FutureBuilder

Now, let's look at a real-world example where we fetch data from an API and display it in a ListView.

Example: Fetching User Names from an API

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class FetchDataExample extends StatelessWidget {
  Future<List<String>> fetchNames() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
    if (response.statusCode == 200) {
      List data = jsonDecode(response.body);
      return data.map((user) => user['name'].toString()).toList();
    } else {
      throw Exception('Failed to load data');
    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Fetch API Data")),
      body: FutureBuilder<List<String>>(
        future: fetchNames(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text("Error: ${snapshot.error}"));
          } else if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(snapshot.data![index]));
              },
            );
          } else {
            return Center(child: Text("No Data Available"));
          }
        },
      ),
    );
  }
}

Conclusion

FutureBuilder is an essential widget for handling asynchronous operations in Flutter. By properly managing different states and optimizing API calls, you can create efficient and responsive applications.

This version enhances clarity, adds better structuring, and improves readability. Let me know if you need any further modifications! 🚀