When should I use a FutureBuilder?
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:
- connectionState.none = In this state future is null
- connectionState.waiting = [future] is not null, but has not yet completed
- 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
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
andisLoadingFlag
- On launch, set
isLoadingFlag = true
, and based on this, showloader
. - Once data arrives, set data with what you get from backend and set
isLoadingFlag = false
(insidesetState
obviously) - We need to have a
if-else
in widget creation. IfisLoadingFlag
istrue
, show theloader
else show thedata
. 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
whosestate
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 thestate
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.