Understanding Future, await in Flutter

I have read most of the documentation on Futures in Flutter. But I still don't understand how to return a none future value from a function.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp( Kitchen());

class Kitchen extends StatelessWidget {
    String caption = '-none-';

    Kitchen({Key? key}) : super(key: key);

    String retrieve() async {
        const String link = 'http://worldclockapi.com/api/json/utc/now';
        Uri url = Uri.parse(link);

        http.Response response = await http.get(url);
        String result = response.toString();

        return result;
    }

    @override
    Widget build(BuildContext context) {
        caption = retrieve();

        return MaterialApp(
            title: 'Flutter Demo',
            home: Scaffold(
                appBar: AppBar(title: const Text('Kitchen async demo') )
                , body: Container(
                      color: Colors.cyan[50]
                    , child: Center( child: Text(caption) )
                )
            )
        );
    }
}

The code will not run because retrieve's return type must be Future as written. Then why use the await keyword if I can never return anything from this function but a Future? Why isn't the return type of the await statement a Future. I don't get it.


I like to think of a future as a "soon-to-be value".

Futures can (and should!) have a type argument explaining what the future will eventually be (you can use void if the future will never be anything other than a future)

Future<String> someLongComputation() async { ... }

above, someLongComputation will immediately return a future, and after some time, said future will resolve with a string.

What the await keyword does is wait until the future has returned a value and then returns said value, basically turning an asynchronous computation into a synchronous one, of course this would negate the whole point of making it asynchronous in the first place, so the await keyword can only be used inside of another asynchronous function.

You can't return a String from an asynchronous function, a String must have a value immediately, but an asynchronous function will never have a value immediately, what you should do is return a future string:

Future<String> retrieve() async {
        const String link = 'http://worldclockapi.com/api/json/utc/now';
        Uri url = Uri.parse(link);

        http.Response response = await http.get(url);
        String result = response.toString();

        return result;
}

But this is a problem because you are trying to display the string on a widget. What do you expect to see while the function runs? What if you have no internet and the function gets stuck for like a minute?

You have a couple of options:

You could use setState and a stateful widget:

class Kitchen extends StatefulWidget{
  ...
}

class _KitchenState extends State<Kitchen> {
    String caption = '-none-';

    Kitchen({Key? key}) : super(key: key);

    Future<void> retrieve() async {
        const String link = 'http://worldclockapi.com/api/json/utc/now';
        Uri url = Uri.parse(link);

        http.Response response = await http.get(url);
        String result = response.toString();

        setState(() => caption = result);
    }

    @override 
    initState() {
      super.initState();
      retrieve();
    }

    @override
    Widget build(BuildContext context) {

        return MaterialApp(
            title: 'Flutter Demo',
            home: Scaffold(
                appBar: AppBar(title: const Text('Kitchen async demo') )
                , body: Container(
                      color: Colors.cyan[50]
                    , child: Center( child: Text(caption) )
                )
            )
        );
    }
}

As you can see I removed the return type and changed it for a call to setState, I also call the function on initstate as opposed to build.

Your second option is using FutureBuilder to accomplish something similar:

class Kitchen extends StatelessWidget {
    Kitchen({Key? key}) : super(key: key);

    Future<String> retrieve() async {
        const String link = 'http://worldclockapi.com/api/json/utc/now';
        Uri url = Uri.parse(link);

        http.Response response = await http.get(url);
        String result = response.toString();

        return result;
    }

    @override
    Widget build(BuildContext context) {

       return FutureBuilder(
         future: retrieve(),
         builder: (context, snapshot) {
           final caption = snapshot.data ?? '-none-'; 
           return MaterialApp(
            title: 'Flutter Demo',
            home: Scaffold(
                appBar: AppBar(title: const Text('Kitchen async demo') ),
                  body: Container(
                    color: Colors.cyan[50], 
                    child: Center( child: Text(caption) )
                )
             )
           ); 
         }
       );
    }

The code above might look ugly, but all you have to understand is that the FutureBuilder widget takes two arguments: future and builder, future is just the future you want to use, while builder is a function that takes two parameters and returns a widget. FutureBuilder will run this function before and after the future completes. The two arguments are the current context and a snapshot, which has some useful things:

You can access the future's result with snapshot.data, if the future has not yet completed, it will be null!

You can also see if there is data with snapshot.hasData.

You can see if something went wrong with snapshot.hasError and snapshot.error.

Finally, you can see the state of the future with snapshot.connectionState, which may have one of 4 values:

  • none, maybe with some initial data.
  • waiting, indicating that the asynchronous operation has begun, typically with the data being null.
  • active, with data being non-null, and possible changing over time.
  • done, with data being non-null.

To check use if (snapshot.connectionState == ConnectionState.done) for example.

To understand snapshot and FutureBuilder better, you can look at the documentation:

Snapshot (actually called async snapshot): https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html

Future Builder: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html