Flutter - Using FutureBuilder Widget Examples

This tutorial shows you how to use FutureBuilder in Flutter.

In Dart, you can create a function that returns Future if you need to perform asynchronous operations. Sometimes, you may want to build a Flutter widget which depends on the result of a Future. In that case, you can use FutureBuilder. FutureBuilder is a widget that uses the result of a Future to build itself. Below are the examples of how to use the widget.

Using FutureBuilder Widget

The FutureBuilder class has a generic parameter which is the data type to be returned by the Future. To use it, you need to call the constructor which can be seen below.

  const FutureBuilder({
    Key? key,
    Future<T>? future,
    T? initialData,
    required AsyncWidgetBuilder<T> builder,
  })

Basically, what you need to do is build a widget using a function passed as the builder argument, based on the snapshot of a Future passed as the future argument.

Create Future

First, we need to create a Future to be passed as the future argument. In this tutorial, we are going to use the function below, which returns Future<String>

  Future<String> getValue() async {
    await Future.delayed(Duration(seconds: 3));
    return 'Woolha';
  }

You need to be careful when passing the Future. If you pass it like the code below.

  FutureBuilder(
    future: getValue(),
    // other arguments
  ),

The getValue function will be called every time the widget is rebuilt. If you don't want the function to be called every time the widget is rebuilt, you must not create the Future inside State.build or StatelessWidget.build method. TheFuture needs to be created earlier, for example during State.initState, State.didChangeDependencies, or State.didUpdateWidget. In the example below, the Future is stored in a state variable.

  Future<String> _value;

  @override
  initState() {
    super.initState();
    _value = getValue();
  }

Then, pass the state variable as the future argument.

  FutureBuilder<String>(
    future: _value,
    // other arguments
  )

Create AsyncWidgetBuilder

You are required to pass an AsyncWidgetBuilder function which is used to build the widget. The function has two parameters. The first parameter's type is BuildContext, while the second parameter's type is AsyncSnapshot<T>. You can use the value of the second parameter to determine the content that should be rendered.

First, you need to understand about AsyncSnapshot. AsyncSnapshot is described as an immutable representation of the most recent interaction with an asynchronous computation. In this case, it represents the latest interaction with a Future. There are some important properties of AsyncSnapshot that can be useful. The first one is connectionState whose type is ConnectionState enum. It indicates the current connection state to an asynchronous computation. In the FutureBuilder's scope, the asynchronous computation is the Future. The possible enum values are:

  • none: Not connected to any asynchronous computation. It can happen if the future is null.
  • waiting: Connected to an asynchronous computation and awaiting interaction. In this context, it means the Future hasn't completed.
  • active: Connected to an active asynchronous computation. For example, if a Stream has returned any value but not completed yet. Should not happen for FutureBuilder.
  • done: Connected to a terminated asynchronous computation. In this context, it means the Future has completed.

Another property you need to know is hasError. It can be used to indicate whether the snapshot contains a non-null error value. If the last result of the asynchronous operation was failed, the value will be true.

To check whether the snapshot contains non-null data, you can use the hasData property. The asynchronous operation has to be complete with non-null data in order for the value to become true. However, if the asynchronous operation completes without data (e.g. Future<void>), the value will be false. If the snapshot has data, you can obtain it by accessing the data property.

Based on the value of the properties above, you can determine what should be rendered on the screen. In the code below, a CircularProgressIndicator is displayed when the connectionState value is waiting. When the connectionState changes to done, you can check whether the snapshot has error or data.

  FutureBuilder<String>(
    future: _value,
    builder: (
      BuildContext context,
      AsyncSnapshot<String> snapshot,
    ) {
      print(snapshot.connectionState);
      if (snapshot.connectionState == ConnectionState.waiting) {
        return CircularProgressIndicator();
      } else if (snapshot.connectionState == ConnectionState.done) {
        if (snapshot.hasError) {
          return const Text('Error');
        } else if (snapshot.hasData) {
          return Text(snapshot.data);
        } else {
          return const Text('Empty data');
        }
      } else {
        return Text('State: ${snapshot.connectionState}');
      }
    },
  )

Set Initial Data

The constructor of FutureBuilder has a named parameter initialData. It can be used to pass the data that will be used to create the snapshots until a non-null Future has completed. Passing a value as the initialData causes the hasData property to have true value at the beginning, even before the Future completes. You can access the initial data using the data property. Once the Future has completed with a non-null value, the value of data will be replaced with a new value returned by the Future. If the Future completes with an error, the hasData and data properties will be updated to false and null respectively.

  FutureBuilder<String>(
    initialData: 'App Name',
    // other arguments
  ),

By setting an initial data, the snapshot can have data even when the connection state is still waiting. Therefore, we need to modify the code above inside the if (snapshot.connectionState == ConnectionState.waiting) block, so that the initial data can be displayed when the connection state is waiting.

  if (snapshot.connectionState == ConnectionState.waiting) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        CircularProgressIndicator(),
        Visibility(
          visible: snapshot.hasData,
          child: Text(
            snapshot.data,
            style: const TextStyle(color: Colors.black, fontSize: 24),
          ),
        ),
      ],
    );
  }

FutureBuilder - Parameters

  • Key? key: The widget's key, used to control how a widget is replaced with another widget.
  • Future<T>? future: A Future whose snapshot can be accessed by the builder function.
  • T? initialData: The data that will be used to create the snapshots until a non-null Future has completed.
  • required AsyncWidgetBuilder<T> builder: The build strategy used by this builder.

Full Code

  import 'package:flutter/material.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        home: FutureBuilderExample(),
        debugShowCheckedModeBanner: false,
      );
    }
  }
  
  Future<String> getValue() async {
    await Future.delayed(Duration(seconds: 3));
  
    return 'Woolha';
  }
  
  class FutureBuilderExample extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
      return _FutureBuilderExampleState ();
    }
  }
  
  class _FutureBuilderExampleState extends State<FutureBuilderExample> {
  
    Future<String> _value;
  
    @override
    initState() {
      super.initState();
      _value = getValue();
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Woolha.com Flutter Tutorial'),
        ),
        body: SizedBox(
          width: double.infinity,
          child: Center(
            child: FutureBuilder<String>(
              future: _value,
              initialData: 'App Name',
              builder: (
                BuildContext context,
                AsyncSnapshot<String> snapshot,
              ) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CircularProgressIndicator(),
                      Visibility(
                        visible: snapshot.hasData,
                        child: Text(
                          snapshot.data,
                          style: const TextStyle(color: Colors.black, fontSize: 24),
                        ),
                      )
                    ],
                  );
                } else if (snapshot.connectionState == ConnectionState.done) {
                  if (snapshot.hasError) {
                    return const Text('Error');
                  } else if (snapshot.hasData) {
                    return Text(
                      snapshot.data,
                      style: const TextStyle(color: Colors.teal, fontSize: 36)
                    );
                  } else {
                    return const Text('Empty data');
                  }
                } else {
                  return Text('State: ${snapshot.connectionState}');
                }
              },
            ),
          ),
        ),
      );
    }
  }

Output:

Flutter - FutureBuilder

Summary

That's how to use FutureBuilder in Flutter. You need to create a Future and pass it as the future argument. The snapshots of the Future will be passed to the builder function, in which you can determine the layout to be displayed based on the current snapshot.

You can also read our tutorials about:

  • StreamBuilder: A widget that builds itself based on the snapshots of a Stream.