This tutorial shows you how to catch and handle errors in Dart, which also works in Flutter.
Error Handling with Future
chain
Future
in Dart is described as an object that represents a delayed computation. It's used to represent a value or an error that will be available in the Future. Usually, it's used for operations that need some time to complete, such as fetching data over a network or reading from a file. Those operations are better to be performed asynchronously and usually wrapped in a function that returns Future
, since you can put asynchronous operations inside a function that returns Future
. Dart supports both Future
chain and async/await
patterns.
While the function is being executed, it may throw an error. You may need to catch the error and determine what to do if an error occurs. Below are the examples of how to handle errors in a Future
chain. For this tutorial, we are going to use the below exception and function.
class MyException implements Exception {}
Future<String> myErrorFunction() async {
return Future.error(new MyException(), StackTrace.current);
}
In the code above, the function throws MyException
using Future.error
, with the stack trace is also passed.
Using then
's onError
If you are already familiar with Dart's Future
, you should have the then
method. It allows you to pass a callback that will be called when the Future
completes. If you look at the signature of then
, there is an optional parameter onError
. The callback passed as the onError
argument will be called when the Future
completes with an error.
The onError
callback must accept one or two parameters. If it accepts one parameter, it will be called with the error. If it accepts two parameters, it will be called with the error and the stack trace. The callback needs to return a value or a Future
.
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
In the code below, myErrorFunction
throws MyException
. The error will be caught by the onError
callback. Inside the callback, you can get the details about the error and the stack trace. You can also set what value to return inside the callback.
myErrorFunction()
.then(
(value) => print('Value: $value'),
onError: (Object e, StackTrace stackTrace) {
print(e.toString());
print(stackTrace);
return 'Another value';
},
)
.then(print);
Output:
Instance of 'MyException'
#0 myErrorFunction (file:///home/ivan/Projects/coba-dart/src/error.dart:9:53)
#1 main (file:///home/ivan/Projects/coba-dart/src/error.dart:17:3)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
Another value
Using catchError
Future
has a method called catchError
which is used to handle errors emitted by the Future
. It's the asynchronous equivalent of a catch block.
Future<T> catchError(Function onError, {bool test(Object error)?})
You need to pass a callback that will be called when the Future
emits an error. Like the onError
callback function of then
in the previous example, the passed callback can have one or two parameters. When the callback is called, the error is passed as the first argument. If the callback accepts two parameters, the stack trace will be passed as the second argument. Below is an example without the test
argument.
myErrorFunction()
.catchError((Object e, StackTrace stackTrace) {
print(e.toString());
print(stackTrace);
return 'Another value';
})
.then(print);
The output of the code above should be the same as the previous example's output.
As you can see on the signature, it also accepts an optional argument test
. For that argument, you can pass a function that accepts the error as the parameter and returns a bool
. If the test
argument is passed and the callback evaluates to true
, the onError
callback (the callback passed as the first argument) will be called. Otherwise, if the test
callback evaluates to false
, the onError
callback will not be called and the returned Future
completes with the same error and stack trace. If the test
argument is not passed, it defaults to a method that returns true
. Below is another example in which the test
argument is passed.
myErrorFunction()
.catchError(
(Object e, StackTrace stackTrace) {
print(e.toString());
print(stackTrace);
return 'Another value';
},
test: (Object error) => error is MyException
)
.then(print);
The output of the code above should be the same as the previous example's output.
Using onError
Future
also has another method called onError
. It can be used to handle errors thrown by the Future
.
Future<T> onError<E extends Object>(
FutureOr<T> handleError(E error, StackTrace stackTrace),
{bool test(E error)?})
The value you need to pass as the first argument is similar to the previous examples, a callback function accepting one or two parameters. The difference is the callback function needs to return a value whose type is the same as the return type of the previous Future
in the chain. Like catchError
, it accepts optional test
argument which is used to handle whether the passed callback should handle the emitted error or not. But you can also specify a specific error type to be caught by passing a generic type (e.g. .onError<MyException>
). All errors with a different error type will not be handled.
myErrorFunction()
.onError<MyException>(
(Object e, StackTrace stackTrace) {
print(e.toString());
print(stackTrace);
return 'Another value';
},
test: (Object error) => error is MyException
);
The output of the code above should be the same as the previous example's output.
Using whenComplete
While catchError
equivalents to catch block, whenComplete
is the equivalent of finally
block. Therefore, if a code must be executed regardless of whether the Future
completes with an error or not, you can use whenComplete
.
Future<T> whenComplete(FutureOr<void> action());
Example:
myErrorFunction()
.catchError(
(Object e, StackTrace stackTrace) {
print(e.toString());
print(stackTrace);
},
test: (Object error) => error is MyException
)
.whenComplete(() { print('complete'); })
.then(print);
Output:
Instance of 'MyException'
#0 myErrorFunction (file:///home/ivan/Projects/coba-dart/src/error.dart:9:53)
#1 main (file:///home/ivan/Projects/coba-dart/src/error.dart:18:11)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
complete
Error Handling with Try-Catch Block
For asynchronous codes with async/await style or for non-asynchronous codes, you can use the try-catch-finally block, which is also common in other programming languages. Dart's catch
accepts either one or two parameters. If an error is thrown, the error will be passed as the first argument. If the catch
block accepts two parameters, the stack trace will be passed as the second argument.
try {
await myErrorFunction();
} catch (e, stackTrace) {
print(e.toString());
print(stackTrace);
} finally {
print('complete');
}
The output should be the same as the output of the previous example (whenComplete
).
Summary
That's how to handle errors in Dart/Flutter. For non-asynchronous codes or for asynchronous codes with async/await style, you can use the try-catch-finally block. When using Future
chain style, you can pass a callback as then
's onError
argument in order to handle errors. You can also use catchError
and whenComplete
which are the equivalents of catch
and finally
respectively.