Brainwave Cafe

Tech Education Blog

June 7, 2024

Understanding Future in Dart and Flutter

Understanding Future in Dart and Flutter

When you write code, you usually expect your instructions to run in order, one after another. For example, if you write:

int x = 5;
int y = x * 2;

You expect y to be 10 because int x = 5 finishes before the next line runs. So, the second line waits for the first one to finish before it runs.

This works fine most of the time, but sometimes, when you have tasks that take longer to finish, this isn’t the best way. Your app would freeze until the task is done. That’s why in almost all modern programming languages, including Dart, you can do things asynchronously.

Asynchronous operations don’t stop the main code from running, so other tasks can be done before the long task finishes.

There are different ways to use future in Dart and Flutter;

  1. Future with then
  2. Future with async/await
  3. Completer
  4. FutureGroup

Using Future with then

To illustrate the use of future with then, we will make an API call from dummyjson Api to get random quote.

Step 1:

Create a new Flutter app, and call it any name of your choice, Add the http dependency, typing: flutter pub add http and Check pubspec.yaml and make sure the dependency has been added.

if you did that right you pubspec.yaml file will contain the following;

dependencies:
  cupertino_icons: ^1.0.6
  flutter:
    sdk: flutter
  http: ^1.2.1

Step 2

In your main.dart file add the create a stateful widget with the body containing an elevated button as shown below;

import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  String result = '';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Using Future with then'),
      ),
      body: Center(
        child: Column(children: [
          const Spacer(),
          ElevatedButton(
            child: const Text('GO!'),
            onPressed: () {},
          ),
          const Spacer(),
          Text(result),
          const Spacer(),
        ]),
      ),
    );
  }
}

Step 3

Now, we need a function that returns a Future. This function will perform some asynchronous operation, like fetching data from the internet or reading a file, in our case fetching random quote from the dummyjson API,

so at the top of the _MyHomePageState class, we will create a function called getQuote to handle our API call and return a response.

NB: import http in your project using (import 'package:http/http.dart' as http; and import 'package:http/http.dart')

Future<Response> getQuote() {
    const authority = 'dummyjson.com';
    const path = '/quotes/random';
    Uri url = Uri.https(authority, path);
    return http.get(url);
  }

Step 4

Finally, when the elevated button is pressed the function will be called and the result variable will be updated with the new value from the API

getQuote().then((value) {
  setState(() {
    result = value.body;
  });
}).catchError((_) {
  setState(() {
    result = 'An error occurred';
  });
});

Final Code

Your final code should look like this

import 'dart:async';

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<Response> getQuote() {
    const authority = 'dummyjson.com';
    const path = '/quotes/random';
    Uri url = Uri.https(authority, path);
    return http.get(url);
  }

  String result = '';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Using Future with then'),
      ),
      body: Center(
        child: Column(children: [
          const Spacer(),
          ElevatedButton(
            child: const Text('GO!'),
            onPressed: () {
              getQuote().then((value) {
                setState(() {
                  result = value.body;
                });
              }).catchError((_) {
                setState(() {
                  result = 'An error occurred';
                });
              });
            },
          ),
          const Spacer(),
          Text(result),
          const Spacer(),
        ]),
      ),
    );
  }
}

Using Future with async/await

Using Future with async and await in Dart makes it easier to work with asynchronous operations by allowing you to write code that looks and behaves more like synchronous code. This approach improves readability and reduces the complexity of handling Future results with nested callbacks.

  • async is used to mark a method as asynchronous, and it should be added before the function body.
  • await is used to tell the framework to wait until the function has finished its execution and returns a value. While the then callback works in any method, await only works inside async methods.

Step 1: Define an Asynchronous Function

To use async and await, you need to define a function as async. This function can then use the await keyword to wait for Future results.

 Future<String> fetchData() async {
    // Simulate a network request with a delay
    await Future.delayed(const Duration(seconds: 2));
    return 'Data fetched';
  }

Step 2: Call the Asynchronous Function with await

When calling an asynchronous function, you use the await keyword to wait for the Future to complete and get the result.

void main() async {
  print('Fetching data...');
  try {
    String data = await fetchData();
    print(data); // Output: Data fetched
  } catch (error) {
    print('Error: $error');
  }
}

Example

Using this UI we used in the previous example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  //Using Future with async/await
  Future<String> fetchData() async {
    // Simulate a network request with a delay
    await Future.delayed(const Duration(seconds: 2));
    return 'Data fetched';
  }

  String result = '';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Using Future with then'),
      ),
      body: Center(
        child: Column(children: [
          const Spacer(),
          ElevatedButton(
            child: const Text('GO!'),
            onPressed: () async {
              String data = await fetchData();
              try {
                setState(() {
                  result = data;
                });
              } catch (error) {
                setState(() {
                  result = 'an error occurred';
                });
              }
            },
          ),
          const Spacer(),
          Text(
            result,
            style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const Spacer(),
        ]),
      ),
    );
  }
}

Using Future with Completer

You can handle most tasks in Dart and Flutter using Future with then, catchError, async, and await. However, there’s another option for dealing with asynchronous programming: the Completer class. With Completer, you can create Future objects and decide when to finish them with a value or an error. In this guide, we’ll show you how to use Completer.

Using Future with Completer in Dart gives you more control over the completion of a Future. A Completer allows you to create a Future and manually control when it completes, either successfully or with an error. This is particularly useful when you need to manage complex asynchronous operations that might not fit well with the simpler async/await or then patterns.

Step 1: Create a Completer

Firstly, you create an instance of Completer. The Completer object will provide a Future that you can return and control.

Completer<String> completer = Completer<String>();

Step 2: Perform the Asynchronous Operation

Perform your asynchronous operation. When the operation is complete, use the Completer to complete the Future.

void performAsyncTask() {
  // Simulate an asynchronous task with a delay
  Future.delayed(Duration(seconds: 2), () {
    // Complete the completer with a value
    completer.complete('Task completed successfully');
    
    // If there was an error, you could use:
    // completer.completeError('An error occurred');
  });
}

Step 3: Return the Future

Return the Future from the Completer so that callers can wait for it to complete.

Future<String> getFuture() {
  performAsyncTask();
  return completer.future;
}

Example

Here is an example demonstrating how to use Future with Completer

import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  //Create a Completer
  Completer<String> completer = Completer<String>();
  
  //Perform the Asynchronous Operation
  Future<String> performAsyncTask() {
    Future.delayed(const Duration(seconds: 2), () {
      completer.complete('Task completed successfully');
    });
    //Return the Future
    return completer.future;
  }

  String result = '';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Using Future with then'),
      ),
      body: Center(
        child: Column(children: [
          const Spacer(),
          ElevatedButton(
            child: const Text('GO!'),
            onPressed: () async {
              // Wait for the Completer's Future to complete
              String data = await performAsyncTask();
              try {
                setState(() {
                  result = data;
                });
              } catch (error) {
                setState(() {
                  result = 'an error occurred';
                });
              }
            },
          ),
          const Spacer(),
          Text(
            result,
            style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const Spacer(),
        ]),
      ),
    );
  }
}

Firing Multiple Future Using FutureGroup

In Dart, managing multiple futures can become cumbersome, especially when you need to wait for all of them to complete. While Dart’s standard library does not include a FutureGroup class, you can achieve similar functionality using the Future.wait method or by leveraging packages like async.

FutureGroup is a collection of Futures that can be run in parallel. As all the tasks run in parallel, the time of execution is generally faster than calling each asynchronous method one after another.

When all the Futures of the collection have finished executing, a FutureGroup returns its values as a List, in the same order they were added into the group.

You can add Futures to a FutureGroup using the add() method, and when all the Futures have been added, you call the close() method to signal that no more Futures will be added to the group.

Here’s how you can use the FutureGroup concept with the async package to manage multiple futures.

Step 1: Add the async Package

First, add the async package to your pubspec.yaml file if it’s not already included.

dependencies:
  async: ^2.8.2

Step 2: Import the Package

FutureGroup is available in the async package, which must be imported into your dart file as shown in the following code block:

import 'package:async/async.dart';

Step 3: Create and Use a FutureGroup

Create an instance of FutureGroup to manage multiple futures.

//Future 1
Future<String> fetchData1() async {
  await Future.delayed(Duration(seconds: 2));
  return 'Data 1 fetched';
}

//Future 2
Future<String> fetchData2() async {
  await Future.delayed(Duration(seconds: 3));
  return 'Data 2 fetched';
}

void main() async {
  FutureGroup<String> futureGroup = FutureGroup<String>();

  // Add futures to the group
  futureGroup.add(fetchData1());
  futureGroup.add(fetchData2());

  // Close the group to signal that no more futures will be added
  futureGroup.close();

  // Wait for all futures in the group to complete
  try {
    List<String> results = await futureGroup.future;
    print('All futures completed:');
    for (String result in results) {
      print(result);
    }
  } catch (error) {
    print('Error: $error');
  }
}

Using Future.wait as an Alternative

If you prefer not to use the async package, you can achieve similar functionality using Dart’s built-in Future.wait:

Future<String> fetchData1() async {
  await Future.delayed(Duration(seconds: 2));
  return 'Data 1 fetched';
}

Future<String> fetchData2() async {
  await Future.delayed(Duration(seconds: 3));
  return 'Data 2 fetched';
}

void main() async {
  try {
    List<String> results = await Future.wait([fetchData1(), fetchData2()]);
    print('All futures completed:');
    for (String result in results) {
      print(result);
    }
  } catch (error) {
    print('Error: $error');
  }
}

Example

Here is an example demonstrating how to use Future with FutureGroup

import 'dart:async';

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // Create Future 1
  Future<String> fetchData1() async {
    Future.delayed(const Duration(seconds: 2));
    return 'Data 1 fetched';
  }

  // Create Future 2
  Future<String> fetchData2() async {
    Future.delayed(const Duration(seconds: 2));
    return 'Data 2 fetched';
  }

  // create FutureGroup
  void returnFG() async {
    FutureGroup<String> futureGroup = FutureGroup<String>();

    // Add futures to the group
    futureGroup.add(fetchData1());
    futureGroup.add(fetchData2());

    //Close the group to signal that no more futures will be added
    futureGroup.close();

    // Wait for all futures in the group to complete
    try {
      List<String> datas = await futureGroup.future;
      for (String data in datas) {
        setState(() {
          result = data;
        });
      }
    } catch (error) {
      setState(() {
        result = 'an error occurred';
      });
    }
  }

  String result = '';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Multiple Future with FutureGroup'),
      ),
      body: Center(
        child: Column(children: [
          const Spacer(),
          ElevatedButton(
            onPressed: returnFG,
            child: const Text('GO!'),
          ),
          const Spacer(),
          Text(
            result,
            style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const Spacer(),
        ]),
      ),
    );
  }
}

Conclusion

Conclusion

Understanding and effectively using Future in Dart and Flutter is crucial for handling asynchronous operations, which are a common requirement in modern applications. By leveraging different techniques, such as using then, async/await, Completer, and FutureGroup, you can manage asynchronous tasks in a more controlled and readable manner.

  • Using then allows you to handle the result of a Future with callbacks, providing a straightforward way to execute code when the asynchronous operation completes.
  • Async/await offers a more readable and synchronous-like way to write asynchronous code, improving the maintainability of your codebase.
  • Completer gives you precise control over the completion of a Future, making it ideal for complex scenarios where you need to manually trigger the completion.
  • FutureGroup, or alternatives like Future.wait, enable you to manage multiple futures efficiently, ensuring that you can handle the results of several asynchronous operations simultaneously.

By mastering these techniques, you can ensure your Dart and Flutter applications remain responsive and efficient, providing a better user experience. Asynchronous programming is a powerful tool in your development toolkit, and knowing when and how to use each method will help you build robust and performant applications.

Article by Lucky Ekpebe / Uncategorized / async await in flutter, dart, flutter, Flutter Future then example, future, future in dart, Future in dart and flutter example, Future in Flutter Leave a Comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Understanding Dart Streams and How to Use Streams in Flutter
  • How to Use FutureBuilder in flutter | Step by Step
  • How to use Futures with StatefulWidgets
  • Understanding Future in Dart and Flutter
  • BloC Vs Riverpod in Flutter

Recent Comments

  1. How to Use FutureBuilder in flutter | Step by Step on How to use Futures with StatefulWidgets

Copyright © 2025 · Education Pro on Genesis Framework · WordPress · Log in