1

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.

  • 1
    You cannot return a non-`Future` value from an `async` function (although you can return `void`). You don't actually want the program to stop and wait for the `http.get` call to complete because if your internet connection is poor then maybe it takes 10 seconds, and your app would consequently be unresponsive for 10 seconds. By returning a `Future`, you can return from the function immediately without waiting/lagging-out the app, and then you can deal with the value at a later point when it arrives (perhaps by using a `FutureBuilder`). – mmcdon20 Jan 23 '22 at 04:39
  • Does this answer your question? [How and when to use ‘async’ and ‘await’](https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await) – Md. Yeasin Sheikh Jan 23 '22 at 05:04

4 Answers4

1

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

h8moss
  • 4,626
  • 2
  • 9
  • 25
0

Here is a working example that prints the current time in the middle of the screen. When the initial widget is created it will print -none- then once the future completes it will produce the current date and time. This is all based on the answer above.

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

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

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

    @override
    _KitchenState createState(){
        return _KitchenState();
    }
}

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

    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);
        Map body = json.decode( response.body );
        String result = body['currentDateTime'];

        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) )
                )
            )
        );
    }
}
0

Working futureBuilder example based on the first answer. Works just like the initState version.

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

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

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

    @override
    _KitchenState createState(){
        return _KitchenState();
    }
}

class _KitchenState extends State<Kitchen> {

    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);
        Map body = json.decode( response.body );
        String result = body['currentDateTime'];
        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.toString() ) )
                )
             )
           );
         }
       );
    }
}
0

Working example with a progress indicator. One way to do a loader I guess.

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

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

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

    //===================================================
    // createState                                      =
    //===================================================
    @override
    _KitchenState createState(){
        return _KitchenState();
    }
}

class _KitchenState extends State<Kitchen> {

    Future<bool> wasteTime() {
        return Future.delayed(const Duration(seconds: 2)).then((onValue) => true);
    }

    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);
        Map body = json.decode( response.body );
        String result = body['currentDateTime'];

// here only to show the CircularProgressIndicator to the user.
//  The network calls are fairly quick.

        await wasteTime();

        return result;
    }

    @override
    Widget build(BuildContext context) {

        return FutureBuilder(
            future: retrieve(),
            builder: (context, snapshot) {
                if( snapshot.connectionState == ConnectionState.done
                    && snapshot.hasError )
                {
                    return MaterialApp(
                        title: 'Flutter Demo',
                        home: Scaffold(
                            appBar: AppBar(title: const Text('Kitchen async demo') ),
                            body: Container(
                                color: Colors.cyan[50],
                                child: const Center( child: Text( 'Error message' ) )
                            )
                        )
                    );
                }
                else if( snapshot.connectionState == ConnectionState.done
                         && snapshot.hasData )
                {
                    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.toString() )
                                )
                            )
                        )
                    );
                }
                else {
                   return MaterialApp(
                        title: 'Flutter Demo',
                        home: Scaffold(
                            appBar: AppBar(title: const Text('Kitchen async demo') ),
                            body: Container(
                                color: Colors.cyan[50],
                                child: const Center(
                                    child: CircularProgressIndicator(
                                        value: null, color: Colors.blue
                                    )
                                )
                            )
                        )
                    );
                }
            }
        );
    }
}