thumbnail

Errors And Exceptions in Dart - Throw, Catch and Finally

Published on 29 March 2020
Last Updated on 29 March 2020

Errors in Dart

Error objects are used for system wide failures such as memory errors.

Both errors and exceptions can be caught, however if an error occurred then the safest thing to do is to terminate the program.

In some cases errors can not be caught such as calling a function with invalid number of arguments.

An error generally happens because of a program failure that could have been avoided by the programmer.

While throwing an error instead of exception, one must make sure that the this behaviour is well intended.

Examples of built in errors

Error for referring to an invalid index in an array

// example of error
main(List<String> args) {
  var numbers = [1, 2, 3, 4, 5];
  try {
    print(numbers[-1]);
  } catch (e) {
    print(e);
  }
}

Above program produces following output:

RangeError (index): Invalid value: Not in range 0..4, inclusive: -1

Process finished with exit code 0

Error for calling a function with invalid number of arguments

int add(int a, int b) {
  return a + b;
}

// example of error
main(List<String> args) {
  try {
    // invalid number of arguments
    // ignore: extra_positional_arguments
    print(add(1, 2, 3));
  } catch (e) {
    print(e);
  }
}

Above program produces following output:

bin/error_2.dart:8:14: Error: Too many positional arguments: 2 allowed, but 3 found.
Try removing the extra positional arguments.
    print(add(1, 2, 3));
             ^
bin/error_2.dart:1:5: Context: Found this candidate, but the arguments don't match.
int add(int a, int b) {
    ^^^

Process finished with exit code 254

How to throw custom errors in Dart

// example demonstrating how to throw errors
main(List<String> args) {
  throw new Error();
}

Above program produces following output:

Unhandled exception:
Instance of 'Error'
#0      main (error.dart:3:3)
#1      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#2      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Exceptions in Dart

An Exception is used for conveying information to user about a failure so that error can be fixed by the programmer. An exception is thrown so that it can be caught. An exception must contain useful information and data fields that can help programmer to easily address the issue.

If an exception is thrown but it is not caught for some reason then isolate that raised the exception is suspended and the isolate and its program is also terminated.

Exceptions can be thrown in following way:

throw Exception("message");

However it is recommended to create and throw custom exceptions that better convey the error message.

Information about built-in exceptions:

Exception Description
FormatException Use this exception when format of a string or number is not appropriate.
Timeout This exception is useful when a particular action times out.
DeferredLoadException Use this exception when a library or asset fails to load in a deferred load.
IntegerDivisionByZeroException This exception can be thrown when an integer is divided by zero.

How to use throw keyword in Dart

Throw keyword can be used for throwing any non null arbitrary object including Exception and Error objects.

However, it is generally recommended to throw objects that implement Error and Exception types.

Throw keyword is used for explicitly raise an exception.

Throw keyword can be used in following ways:

throw Exception("message");

Errors can be thrown in following way:

throw new Error();

Built in error types can be used in following ways:

throw new RangeError('range error');
throw new ArgumentError();

Throwing exception is a statement, this means that it can be thrown anywhere including lambda functions.

throwUnknownErrorException() => throw Exception();

main(List<String> args) {
  // calling lambda function
  throwUnknownErrorException();
}

Code given above makes use of lambda functions for throwing exception.

Above program produces following output:

Unhandled exception:
Exception
#0      throwUnknownErrorException (file:///mnt/data/data/dart/dart_book/bin/throw_1.dart:1:33)
#1      main (file:///mnt/data/data/dart/dart_book/bin/throw_1.dart:5:3)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Process finished with exit code 255

When an exception is raised, it must be handled appropriately otherwise program exits abruptly as shown in above example.

Program demonstrating how to use throw keyword

Given below is a program that makes use of built in exceptions to throw errors when salaryAmount is negative.

void giveSalary(int salaryAmount) {
  if (salaryAmount < 1) {
    throw FormatException('Salary must be a positive number.');
  } else {
    print('salary can be given.');
  }
}

main(List<String> args) {
  // calling lambda function
  giveSalary(-1);
}

Above program produces following output:

Unhandled exception:
FormatException: Salary must be a positive number.
#0      giveSalary (throw_2.dart:3:5)
#1      main (file:///mnt/data/data/dart/dart_book/bin/throw_2.dart:11:3)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Creating and Throwing Custom Exceptions

Program given below makes use of custom exception and how throw keyword can be used for throwing custom exceptions.

// throwing custom exceptions in Dart
class SalaryNegativeException implements Exception {
  final message = 'Salary must be a positive number';
}

void giveSalary(int salaryAmount) {
  if (salaryAmount < 1) {
    throw SalaryNegativeException();
  } else {
    print('salary can be given.');
  }
}

main(List<String> args) {
  // calling lambda function
  giveSalary(-1);
}

Above program produces following output:

Unhandled exception:
FormatException: Salary must be a positive number.
#0      giveSalary (throw_2.dart:3:5)
#1      main (file:///mnt/data/data/dart/dart_book/bin/throw_2.dart:11:3)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

As shown in above program, throw keyword can be used for throwing a custom exception.

Creating and throwing custom errors

Program given below demonstrates how custom errors can be created by extending from the base Error class.

// example demonstrating creation and use of custom errors
class CustomError extends Error {}

main(List<String> args) {
  throw new CustomError();
}

Above program produces following output:

Unhandled exception:
Instance of 'CustomError'
#0      main (file.dart:5:3)
#1      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#2      _RawReceivePortImpl._handleMessage
(dart:isolate-patch/isolate_patch.dart:168:12)

Try Catch On Finally and Rethrow

Code that might cause an exception is put in try block. When an exception is thrown by subsequent code, it is caught by the catch block.

Catch block captures the exception and stops the exception from further propagating unless rethrow is called.

General syntax used by try, catch, on blocks.

try{
  // code that can throw exceptions;
}
on ExceptionType1{
  // for handling exceptions of ExceptionType1
}
catch(e){
  // for handling any exception
}
finally{ // finally block is optional
  // code that must always execute regardless of what exception is thrown
}

Example that demonstrates use of Try, On Block

Program using try, on block:

// example of simple try on block
void main() {
  try {
    // can't parse int because of invalid format
    int.parse('10ABC');
  } on FormatException catch (e, s) {
    print('FormatException Occurred');
    print('Exception info: ${e}');
    print('Stack Trace: ${s}');
  }
}

Above program will produce following output:

FormatException Occurred
Exception info: FormatException: Invalid radix-10 number (at character 1)
10ABC
^

Stack Trace: #0      int._throwFormatException (dart:core-patch/integers_patch.dart:133:5)
#1      int._parseRadix (dart:core-patch/integers_patch.dart:144:16)
#2      int._parse (dart:core-patch/integers_patch.dart:102:12)
#3      int.parse (dart:core-patch/integers_patch.dart:65:12)
#4      main (file:///dart_book/bin/exception_2.dart:4:9)
#5      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#6      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Program using simple Try, Catch block

Program given below demonstrates use of simple try catch block.

// example of simple try catch block
void main() {
  try {
    // can't parse int because of invalid format
    int.parse('10ABC');
  } catch (e, s) {
    print('FormatException Occurred');
    print('Exception info: ${e}');
    print('Stack Trace: ${s}');
  }
}

Above program will produce following output:

FormatException Occurred
Exception info: FormatException: Invalid radix-10 number (at character 1)
10ABC
^

Stack Trace: #0      int._throwFormatException (dart:core-patch/integers_patch.dart:133:5)
#1      int._parseRadix (dart:core-patch/integers_patch.dart:144:16)
#2      int._parse (dart:core-patch/integers_patch.dart:102:12)
#3      int.parse (dart:core-patch/integers_patch.dart:65:12)
#4      main (file:///bin/exception_3.dart:5:9)
#5      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#6      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Finally block

Finally block is optional block that includes any code that must execute regardless of whatever exception that is thrown.

Finally block if defined, executes unconditionally after try, on and catch blocks have been evaluated.

Syntax for finally block appears as it is shown in above syntax example.

Program using finally block

// example using finally block
void main() {
  try {
    // can't parse int because of invalid format
    int.parse('10ABC');
  } catch (e) {
    print('inside catch block');
  } finally {
    print('inside finally block');
  }
}

Above program will produce following output:

inside catch block
inside finally block

As demonstrated by above code, code inside finally block is executed unconditionally.

Rethrow keyword

Rethrow is used for partially handling an exception while allowing it to propagate further. Rethrow can be used to make sure that exception is properly handled internally before it propagates to other callers.

// example demonstrating rethrow block
void divideByZero() {
  try {
    5 % 0;
  } catch (e) {
    print('Exception caught inside divideByZero function: ${e}\n');
    rethrow;
  }
}

void main() {
  try {
    divideByZero();
  } catch (e) {
    print('Exception caught inside main function: ${e}\n');
  }
}

Above program will produce following output:

Exception caught inside divideByZero function: IntegerDivisionByZeroException

Exception caught inside main function: IntegerDivisionByZeroException

As demonstrated by above code, rethrow keyword helps to internally handle an exception and it also lets other callers handle this exception.