This article walks you through 3 different ways to cancel a future in Flutter and Dart.
Using async package (recommended)
The async package is developed and published by the authors of the Dart programming language. It provides utility classes in the style of dart:async to enhance asynchronous computations. The thing that can help us cancel a future is the CancelableOperation class:
var myCancelableFuture = CancelableOperation.fromFuture(
Future<T> inner,
{ FutureOr onCancel()? }
)
// call the cancel() method to cancel the future
myCancelableFuture.cancel();
For more clarity, please see the practical example below.
Complete Example
App Preview
The app we’re going to build has a floating button. When this button gets pressed, an asynchronous operation will start (this takes 5 seconds to finish). The button’s background changes from indigo to red and its label changes from “Start” to “Cancel” and now you can use it to cancel the future.
- If you touch the Cancel button within 5 seconds before the future is completed, the screen will show “Future has been canceled”.
- If you do nothing then after 5 seconds the screen will show “Future completed”.
A demo is worth more than a thousand words:
The Code
1. Install the async package by performing this:
flutter pub add async
Then run:
flutter pub get
2. The full source code in main.dart (with explanations):
// main.dart
import 'package:flutter/material.dart';
import 'package:async/async.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
// Remove the debug banner
debugShowCheckedModeBanner: false,
title: 'KindaCode.com',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: const HomePage());
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// this future will return some text once it completes
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 5));
return 'Future completed';
}
// keep a reference to CancelableOperation
CancelableOperation? _myCancelableFuture;
// This is the result returned by the future
String? _text;
// Help you know whether the app is "loading" or not
bool _isLoading = false;
// This function is called when the "start" button is pressed
void _getData() async {
setState(() {
_isLoading = true;
});
_myCancelableFuture = CancelableOperation.fromFuture(
_myFuture(),
onCancel: () => 'Future has been canceld',
);
final value = await _myCancelableFuture?.value;
// update the UI
setState(() {
_text = value;
_isLoading = false;
});
}
// this function is called when the "cancel" button is tapped
void _cancelFuture() async {
final result = await _myCancelableFuture?.cancel();
setState(() {
_text = result;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('KindaCode.com')),
body: Center(
child: _isLoading
? const CircularProgressIndicator()
: Text(
_text ?? 'Press Start Button',
style: const TextStyle(fontSize: 28),
),
),
// This button is used to trigger _getDate() and _cancelFuture() functions
// the function is called depends on the _isLoading variable
floatingActionButton: ElevatedButton(
onPressed: () => _isLoading ? _cancelFuture() : _getData(),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
backgroundColor: _isLoading ? Colors.red : Colors.indigo),
child: Text(_isLoading ? 'Cancel' : 'Start'),
),
);
}
}
Using timeout() method
This approach is quick and simple. However, it isn’t very flexible.
With the timeout() method, you can limit time (e.g. 3 seconds) for a future. If the future completes in time, its value will be returned. On the other hand, if the future exceeds the limit time, the onTimeout function will be executed instead:
Future<T> timeout(
Duration timeLimit,
{FutureOr<T> onTimeout()?}
)
Quick Example
Create a dummy future:
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 10));
return 'Future completed';
}
Setting a timeout of 3 seconds:
_myFuture().timeout(
const Duration(seconds: 3),
onTimeout: () =>
'The process took too much time to finish. Please try again later',
);
Converting the future to a stream
You can use the asStream() method of the Future class to create a stream that contains the result of the original future. Now you can cancel a subscription to that stream.
Quick Example
// don't forget to import this
import 'dart:async';
// Create a demo future
Future<dynamic> _loadData() async {
await Future.delayed(const Duration(seconds: 10));
return 'Some Data';
}
// a reference to the stream subscription
// so that we can call _sub.cancel() later
StreamSubscription<dynamic>? _sub;
// convert the future to a stream
_sub = _loadData().asStream().listen((data) {
// do something with "data"
print(data);
});
// cancel the stream subscription
_sub.cancel();
Note that this quick example only briefly describes how things work. You have to modify it to make it runnable in your existing project.
Conclusion
You’ve learned more than one approach to canceling a future in Flutter. Choose one of them to implement in your app to make it more robust and engaging when dealing with asynchronous tasks. If you’d like to explore more new and interesting things in Flutter, take a look at the following articles:
- Flutter: Firing multiple Futures at the same time with FutureGroup
- Flutter FutureBuilder example (null safety)
- Flutter StreamBuilder examples (null safety)
- Working with Timer and Timer.periodic in Flutter
- 2 Ways to Fetch Data from APIs in Flutter
You can also take a tour around our Flutter topic page and Dart topic page to see the latest tutorials and examples.