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;
Future
withthen
Future
withasync/await
- Completer
- 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 aFuture
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 aFuture
, making it ideal for complex scenarios where you need to manually trigger the completion.FutureGroup
, or alternatives likeFuture.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.
Leave a Reply