103

I was wondering when I should use the future builder. For example, if I want to make an http request and show the results in a list view, as soon as you open the view, should I have to use the future builder or just build a ListViewBuilder like:

new ListView.builder(
        itemCount: _features.length,
        itemBuilder: (BuildContext context, int position) {
...stuff here...
}

Moreover, if I don't want to build a list view but some more complex stuff like circular charts, should I have to use the future builder?

Hope it's clear enough!

nvoigt
  • 75,013
  • 26
  • 93
  • 142
Little Monkey
  • 5,395
  • 14
  • 45
  • 83
  • 3
    You dont have to, you could also define a List with widgets, and update it with setState. The listview will update when you set the state. `doStuff()async{ var stuff = await getStuff(); setState((){list = stuff;});}` if you use a StatefulWidget ofcourse. – FoxyError Aug 23 '18 at 10:01
  • Understood. Thanks! – Little Monkey Aug 23 '18 at 11:34

3 Answers3

188

FutureBuilder removes boilerplate code.

Let's say you want to fetch some data from the backend on page launch and show a loader until data comes.

Tasks for ListBuilder:

  • Have two state variables, dataFromBackend and isLoadingFlag
  • On launch, set isLoadingFlag = true, and based on this, show loader.
  • Once data arrives, set data with what you get from backend and set isLoadingFlag = false (inside setState obviously)
  • We need to have a if-else in widget creation. If isLoadingFlag is true, show the loader else show the data. On failure, show error message.

Tasks for FutureBuilder:

  • Give the async task in future of Future Builder
  • Based on connectionState, show message (loading, active(streams), done)
  • Based on data(snapshot.hasError), show view

Pros of FutureBuilder

  • Does not use the two state variables and setState
  • Reactive programming (FutureBuilder will take care of updating the view on data arrival)

Example:

FutureBuilder<String>(
    future: _fetchNetworkCall, // async work
    builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
       switch (snapshot.connectionState) {
         case ConnectionState.waiting: return Text('Loading....');
         default:
           if (snapshot.hasError)
              return Text('Error: ${snapshot.error}');
           else
          return Text('Result: ${snapshot.data}');
        }
      },
    )

Performance impact:

I just looked into the FutureBuilder code to understand the performance impact of using this.

  • FutureBuilder is just a StatefulWidget whose state variable is _snapshot
  • Initial state is _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
  • It is subscribing to future which we send via the constructor and update the state based on that.

Example:

widget.future.then<void>((T data) {
    if (_activeCallbackIdentity == callbackIdentity) {
      setState(() {
        _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
      });
    }
}, onError: (Object error) {
  if (_activeCallbackIdentity == callbackIdentity) {
    setState(() {
      _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error);
    });
  }
});

So the FutureBuilder is a wrapper/boilerplate of what we do typically, hence there should not be any performance impact.

Dinesh Balasubramanian
  • 20,532
  • 7
  • 64
  • 57
  • 3
    So I don't need it, but this decision would be a bad one since it helps me a lot. Thanks! – Little Monkey Aug 23 '18 at 11:33
  • Very good question. After your question, I looked into the flutter code. @SamarthAgarwal I update the answer related to performance. Thanks – Dinesh Balasubramanian Aug 28 '18 at 16:51
  • @DineshBalasubramanian thanks for the quick update. Also, are you aware of any side effects of using `setState()` inside the future that we use with `FutureBuilder`. I tried that and turns out that the future is executed recursively. Can you suspect something? – Samarth Agarwal Aug 28 '18 at 16:55
  • U mean `setState` inside `future` method?. Can you provide some simple code snippet – Dinesh Balasubramanian Aug 28 '18 at 17:48
  • What if I want to fetch data on press of a button and show circular progress indicator till that is done? Can `FutureBuilder` be used now? If yes, How? – Keerti Purswani Apr 26 '19 at 07:26
  • @Keerti purswani make a future method where you are fetching data from the backend and then call that method in the future builder. If you want more information [click here](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) – Ajay Thakur May 02 '20 at 21:05
  • why not check against `ConnectionState.done` rather than against `ConnectionState.waiting` ? – Kirill Karmazin May 20 '20 at 09:33
  • had a question about future builder, declared a variable inside build method, now i need to use it outside of the build method https://stackoverflow.com/questions/70980002/flutter-use-variable-created-inside-future-builder-outside-of-the-build-method – LearnFlutter Feb 04 '22 at 09:27
24

FutureBuilder Example

  • When you want to rander widget after async call then use FutureBuilder()

    class _DemoState extends State<Demo> {
    
    @override
    Widget build(BuildContext context) {
      return FutureBuilder<String>(
        future: downloadData(), // function where you call your api
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {  // AsyncSnapshot<Your object type>
          if( snapshot.connectionState == ConnectionState.waiting){
              return  Center(child: Text('Please wait its loading...'));
          }else{
              if (snapshot.hasError)
                return Center(child: Text('Error: ${snapshot.error}'));
              else
                return Center(child: new Text('${snapshot.data}'));  // snapshot.data  :- get your object which is pass from your downloadData() function
          }
        },
      );
    }
    Future<String> downloadData()async{
      //   var response =  await http.get('https://getProjectList');    
      return Future.value("Data download successfully"); // return your response
    }
    }
    

In future builder, it calls the future function to wait for the result, and as soon as it produces the result it calls the builder function where we build the widget.

AsyncSnapshot has 3 state:

  1. connectionState.none = In this state future is null

  2. connectionState.waiting = [future] is not null, but has not yet completed

  3. connectionState.done = [future] is not null, and has completed. If the future completed successfully, the [AsyncSnapshot.data] will be set to the value to which the future completed. If it completed with an error, [AsyncSnapshot.hasError] will be true
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
Sanjayrajsinh
  • 15,014
  • 7
  • 73
  • 78
  • 3
    It should be noted that the downloadData() function will be executed for every time through the build(...) function. Best practice is to call downloadData() and save the returned Future to a variable in your State object, and reference that variable in the future: parameter – Sean Langley Oct 06 '21 at 20:39
18

FutureBuilder is a Widget that will help you to execute some asynchronous function and based on that function’s result your UI will update.

I listed some use cases, why you will use FutureBuilder?

  1. If you want to render widget after async task then use it.

  2. We can handle loading process by simply using ConnectionState.waiting

  3. Don't need any custom error controller. Can handle error simply dataSnapshot.error != null

  4. As we can handle async task within the builder we do not need any setState(() { _isLoading = false; });

When we use the FutureBuilder widget we need to check for future state i.e future is resolved or not and so on. There are various State as follows:

  1. ConnectionState.none: It means that the future is null and initialData is used as defaultValue.

  2. ConnectionState.active: It means the future is not null but it is not resolved yet.

  3. ConnectionState.waiting: It means the future is being resolved, and we will get the result soon enough.

  4. ConnectionState.done: It means that the future has been resolved.

A simple implementation

Here OrdersProvider is a provider class and fetchAndSetOrders() is the method of that provider class.

body: FutureBuilder(
        future: Provider.of<OrdersProvider>(context, listen: false)
            .fetchAndSetOrders(),
        builder: (context, dataSnapshot) {
          if (dataSnapshot.connectionState == ConnectionState.waiting) {
            return Center(
              child: CircularProgressIndicator(),
            );
          } else {
            if (dataSnapshot.error != null) {
              return Center(
                child: Text('An error occured'),
              );
            } else {
              return Consumer<OrdersProvider>(
                builder: (context, orderData, child) => ListView.builder(
                  itemCount: orderData.orders.length,
                  itemBuilder: (context, i) => OrderItem(orderData.orders[i]),
                ),
              );
            }
          }
        },
      ),
Mijanur Rahman
  • 1,094
  • 1
  • 11
  • 20