A correct Flutter FutureBuilder example (for SharedPreferences, REST services, and database access)

At the time of this writing (September 19, 2019), there’s a lot of bad information in books and on the internet about how to use a Flutter FutureBuilder. That bad information caused me to waste a lot of time over the last two days. I don’t have time today to explain everything, but in short, here’s a technically correct FutureBuilder example:

import 'package:flutter/material.dart';
import 'shared_prefs_helper.dart';

class PreferencesEnableNotifications extends StatefulWidget {
    @override
    PreferencesEnableNotificationsState createState() => PreferencesEnableNotificationsState();
}

class PreferencesEnableNotificationsState extends State<PreferencesEnableNotifications> {

    @override
    Widget build(BuildContext context) {

        return Scaffold(
            appBar: AppBar(
                title: Text("Enable Notifications"),
            ),
            body: FutureBuilder<bool>(
                future: SharedPreferencesHelper.getEnableNotifications(),  //returns bool
                builder: (BuildContext context, AsyncSnapshot snapshot) {
                    if (snapshot.connectionState == ConnectionState.done) {
                        // YOUR CUSTOM CODE GOES HERE
                        return CheckboxListTile(
                            title: const Text('Enable Notifications'),
                            value: snapshot.data,  //the bool in the SharedPreferences
                            onChanged: (val) {
                                setState(() {
                                    SharedPreferencesHelper.setEnableNotifications(val);
                                });
                            }
                        );
                    } else {
                        return new CircularProgressIndicator();
                    }
                }
            )
        );
    }
}

The solution

The key part of this proper solution is everything inside of the “builder:” code. The most important thing to do is to show something like the CircularProgressIndicator while the FutureBuilder is waiting for the data to arrive. In this example the data comes from a Flutter SharedPreference, specifically this method which returns a Future:

future: SharedPreferencesHelper.getEnableNotifications(),

Why this is important

If you don’t write the FutureBuilder code as shown, you’ll be in for a world of hurt when your data is returned slowly (in my case, the data from the SharedPreferencesHelper.getEnableNotifications() method). If your data comes back in 10ms or so, you might be fine without the code shown, but if/when your data comes back more slowly, you’ll get some really ugly exceptions written in yellow text on a red background if you don’t use the code shown. (Just use the code shown. Always.)

I’ll try to write more when I have time, but in short, if you want to create a Flutter FutureBuilder that runs without errors when your data is returned slowly, I can confirm that this approach works successfully.

Update: Still not 100% correct

As I tried to do more complicated things with my app, I found out that the solution above is still not 100% correct, even though it’s much better than other documentation that’s out there.

The big thing I have learned is that you should not directly call a function in the “future: ...” part of the code. Because your build method may be called many times — think 60 times a second — if you call a function in that area, you’ll end up calling that function many times. Instead, what you’re supposed to do is create your future as a class-level variable in a State method like initState.

I’ll show how to do this when I have more time, but this is actually an extremely important point. I found this out the hard way when I started adding some animation into my build method, and suddenly the function shown in the original code above was being called many times, instead of just once.