Decode FutureBuilder

An in-depth look at Flutter’s FutureBuilder Widget

class FutureBuilder as of April 03, 2019

I Like Screenshots. Click For Gists.

As always, I prefer using screenshots over gists to show concepts rather than just show code in my articles. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code they represent in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program on our computers — not on our phones. For now.

Other Stories by Greg Perry

Learn By Example

We will use an example from the Flutter Cookbook, fetch data from the internet, to demonstrate a FutureBuilder in action. Below is a screenshot of part of that example displaying a StatelessWidget and how its parameter, post, is provided to a FutureBuilder widget as an instantiated Future object.

Fetch and display the data
class FutureBuilder as of April 03, 2019

A Future In Generics

Looking above at the first line of the FutureBuilder class itself, we see that ‘generic data types’ are utilized with a capital ‘T’ and <…> notation. It’s used to indicate what ‘type of data’ returned from an asynchronous operation. In the cookbook example, the ‘type of data’ is a class of type, Post. Note, using a FutureBuilder, the type could be any of the built-in types offered by Flutter as well.

How This Goes

In the screenshot of the example above, you can see the FutureBuilder widget is being called as the ‘child’ argument of a Center widget. Let’s do this in ‘slow-mo’ and see what happens next with the FutureBuilder being called at this point. What would normally take milliseconds, we’ll step through.

It’s All in the Init

Remember, the FutureBuilder is a StatefulWidget. Thus, the initState() function of the StatefulWidget’s accompanying State object is soon called. It’s there where things really start rolling. You can see in the screenshot below, an object of type, AsyncSnapshot, is instantiated in the function, initState().

iniState() in FutureBuilder as of April 03, 2019

Give An Init Value

Below, is a screenshot of a different example where the FutureBuilder is supplied an ‘initial data’ value — not a class of type, Post, but a boolean data type, false. It’s that value that is then passed to the AsyncSnapshot constructor in the function, initState(), listed above as, widget.initialData.

FutureBuilder() function as of April 03, 2019

The Future Has Class

Again, looking back at the cookbook example, the specific Future object is a class of type, Post, and is provided by the method, fetchPost(). This method is called right away in the main() function and supplied as a parameter to the StatelessWidget, MyApp.

The Complete Example as of April 03, 2019
The Complete Example as of April 03, 2019
At first, an ‘incomplete’ Future object

It Starts with a Build

Like any StatefulWidget, when the FutureBuilder is first called its accompanying State object will first run its initState() function. Soon after, the State object’s build() function is called for the first time. The screenshot below displays that build() function.

FutureBuilder’s build() function as of Apr 3, 2019
The Complete Example as of April 03, 2019

Circular Progress At First

And so, with this first build of the widget tree, a ‘loading spinner’ appears in the center of the screen. Now, why is that? Remember, an ‘incomplete’ Future object of type, Post, had been passed to the FutureBuilder at the start of all this, and when the FutureBuilder’s build() function is then executed that anonymous method is run.

Cookbook example as of April 03, 2019

Null Has No Data

Again, with the first build, the value, snapshot.hasData, is false and results in a loading spinner displayed in the center of the screen. You can see below that this boolean expression, snapshot.hasData, comes from a ‘getter’ in the AsyncSnapshot object which checks if the property, data, is null or not.

hasData in Class AsyncSnapshot as of April 03, 2019

Data of Type T

With the use of Generics, the AsyncSnapshot object defined in the FutureBuilder’s initState() function will have the property called, data, defined as a class of type, Post. Again, if there was an initialData value passed to the FutureBuilder, the property, data, would have been set that value. Otherwise, like in this case, the property, data, is set to null with the Future object not yet able to provide a resulting value.

Generic data type, T, for the property data in Class AsyncSnapshot

Any Errors?

A good practice is to also include in the anonymous method assigned to the parameter, builder, a call to the getter, hasError. It is also found in the AsyncSnapshot object, and also involves checking if a property is null or not. In this case, it’s the property called, error.

hasError in Class AsyncSnapshot as of April 03, 2019

So, What Happens Next?

In this cookbook example with its first build, the Future object is incomplete. So what happens next? What happens next depends on that specified Future object itself. After all, it’s yet to complete its asynchronous operation at this point. Remember that method called, _subscribe(), called in the FutureBuilder’s initState() function? It’s there where the real magic happens.

Where the Magic Happens

A screenshot of the _subscribe() method is displayed below. It’s in this method that the then function is defined for the Future object originally specified with the FutureBuilder. And so, when the Future object complete’s its asynchronous operation, the anonymous method defined and passed as the callback function in the then function will be called. In this case, it’s called when data is fetched from a website.

_subscribe() in _FutureBuilderState<T> as of April 03, 2019

The Future Completes

So what happens next? Well, in this example, until that data is fetched from the website there’s a spinner doing its thing in the center of the screen. However, as you see below, when the asynchronous operation does complete, and the data is fetched from the website either successfully or in error, a brand new immutable AsyncSnapshot object is then created with the enumerated type, ConnectionState.done, passed to it.

_subscribe() in _FutureBuilderState<T> as of April 03, 2019
FutureBuilder’s build() function as of April 03, 2019

The Connection State of the Future Flow

Let’s review the flow of control once again. At first build, we know the Future object is not ready yet (we see the spinner in the center of the screen for a time). Hence, if we looked at the snapshot in the ‘builder’ method at that point, we’d find the value of snapshot.connectionState is set to ConnectionState.waiting.

Cookbook example with Switch statement
initState() in FutureBuilder as of April 03, 2019

Set We’re Waiting

It’s in the function, _subscribe(), where we see the new connection state. By their very nature, asynchronous operations take time. In most cases, the Future object is still incomplete by the time the _subscribe() function is called. Giving that function time to define the Future object’s then function. Note, after the then function is defined, a brand new immutable AsyncSnapshot object is created replacing the old one and now supplying a connection state set of ‘waiting.’

_subscribe() in _FutureBuilderState<T> as of April 03, 2019

When It’s Done, It’s Data

In the screenshots below, we see the progression of the asynchronous operation. While waiting, we see a Text widget was called to display the line, ‘ConnectionState.waiting’, in the center of the screen. However, in an instant, the value of the snapshot.connectionState is set: ConnectionState.done. A breakpoint was placed there at the instant.

So What Happened Back There?

When the asynchronous operation completed, the callback function in the Future object’s then function will run. So in the then function for that specific Future object, when the data fetch was finally completed, the parameter, data, was passed to the ‘withData’ constructor to create yet another brand new immutable AsyncSnapshot object.

_subscribe() in _FutureBuilderState<T> as of April 03, 2019

What Happens In Error?

So, that was great when everything comes together and works. Let’s see what happens when we introduce a malformed URI to the cookbook example. We’ll see how the FutureBuilder widget and its accompanying AsyncSnapshot object reacts when the asynchronous operation fails.

Cookbook example as of April 03, 2019
Cookbook example as of April 03, 2019
_subscribe in FutureBuilder as of Apr 3, 2019
FutureBuilder’s build() function as of Apr 3, 2019
Cookbook example as of April 03, 2019
Property, error, in AsyncSnapshot class

TLDR.

That should be enough. Don’t you think? The rest of this article is just gravy.

What’s with this callbackIdentity?

In the _subscribe() function, you’ll notice two if statements involving the following expression, _activeCallbackIdentity == callbackIdentity. They’re just before the two setState() functions. It’s a little trick that involves the use of the base class for all Dart objects called….well….Object. This object is defined near the start of the function and assigned to a final variable: callbackIndentity = Object();

_subscribe() in _FutureBuilderState<T> as of April 03, 2019
_subscribe() in _FutureBuilderState<T> as of April 03, 2019
Class FutureBuilder as of April 03, 2019
Class FutureBuilder as of April 03, 2019

The Future has been Disposed

Again, the expression, _activeCallbackIdentity == callbackIdentity, is also used because there may be the case the Future object’s asynchronous operation completes, only to find the FutureBuilder widget has been terminated and its State object disposed of. In other words, the expression, _activeCallbackIdentity == callbackIdentity, is false since the property, _activeCallbackIdentity, is set to null.

Class FutureBuilder as of April 03, 2019

An Optional Future?

Did you note, in the _subscribe() function, the code is enclosed in one if statement? At first glance, this implies that the FutureBuilder need not provide a Future object at all to its constructor. What it does mean, however, is that the Future object provided can be null at one time or another during the FutureBuilder’s lifecycle.

_subscribe in FutureBuilder as of Apr 3, 2019

A Builder is a Must

Looking at the FutureBuilder class we see that, in fact, it’s the property, builder, that must be passed to the constructor. We see the annotation, @required, and an assert statement stresses that fact. By the very nature of named parameters, however, the constructor parameters, future and initialData, are optional. Hence, the property, future, can be null.

FutureBuilder as of Apr 3, 2019

No Future No Data No Error

What happens if you didn’t supply a Future object to that cookbook example? Hopefully, after reading this article, you can deduce that a spinner will appear in the center of the screen.

Why No Future?

Again, the Future parameter is not intended to be optional. It’s to allow the passed Future object to be null at some point in time— usually at the start. For example, if the cookbook example app had the FutureBuiler in a State object and not in a StatelessWidget, this implies that the FutureBuilder could be called again and again (the widget rebuilt again and again), and thus allowing a supplied Future object to be null for one reason or another during one of those calls.

It’s a Future with a Then

So, what’s a FutureButilder in a nutshell? Essentially, a FutureBuilder is a Future object assigned a then method while inside a StatefulWidget. The anonymous function defined in the then method fires off setState() functions when the asynchronous operation finally completes successfully or not. This, of course, fires the build() function of the enclosing State object allowing the anonymous function supplied by the FutureBuilder’s named parameter, builder, to run again returning the appropriate widget based on the outcome of the asynchronous operation.

Decode Flutter Series
DECODE Flutter on YouTube

Freelance Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store