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 thefuture
is null.waiting
: Connected to an asynchronous computation and awaiting interaction. In this context, it means theFuture
hasn't completed.active
: Connected to an active asynchronous computation. For example, if aStream
has returned any value but not completed yet. Should not happen forFutureBuilder
.done
: Connected to a terminated asynchronous computation. In this context, it means theFuture
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
: AFuture
whose snapshot can be accessed by thebuilder
function.T? initialData
: The data that will be used to create the snapshots until a non-nullFuture
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:
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 aStream
.