Exceptions and Error Handling in Dart and Flutter

Exceptions and Error Handling in Dart and Flutter

Why Have Error & Exception Handling?

Most software systems are complicated and written by a team of people.

 

Complexity arises from multiple sources:

  • The business domain.
  • The act of writing software.
  • From multiple people working together, each one having different viewpoints.
  • etc

 

The complexity can result in misunderstandings, errors & exceptions.

 

This is not the end of the world if the code has good error handling.

  • If you don't handle your errors & exceptions, your software may act unpredictably, and users may suffer a catastrophic error without knowing it or being able to detect when it happened.
  • If you do handle your errors & exceptions, the user may able to continue using the program even with the error / exception and the developers can find the problems over time and improve the software.

 

Good error & exception handling should not blind the end user with technical jargon, but it should also provide enough information for the developers to trace down the problem.

 

Dart can throw Errors & Exceptions when problems occur running a Dart program. When an Error or an Exception occurs, normal flow of the program is disrupted, and the program terminates abnormally.

 

Errors and Exceptions

Errors

Errors are serious issues that cannot be caught and ‘dealt with’. Non-recoverable.

Examples

  • RangeError – programmatic bug where user is attempting to use an invalid index to retrieve a List element.
  • OutOfMemoryError

Exceptions

Exceptions are less-serious issues that can be caught and ‘dealt with’.  

Recoverable.

Examples

  • FormatException – could not parse a String.

 

Handling Errors

Trying to handle non-recoverable errors is impossible. How can you catch and just handle an out of memory error?

The best thing to do is to log what happened and where so that the developers can deal with them. The approach to this is to add a handler to the top level of your application, for example Sentry or Catcher.

Further Reading

https://medium.com/flutter-community/handling-flutter-errors-with-catcher-efce74397862

 

Handling Exceptions

Try to handle these to prevent the application from terminating abruptly. If you want your code to handle exceptions then you need to place it in a ‘try..catch..finally’ block. The finally part is optional.

Finally

Dart also provides a finally block that will always be executed no matter if any exception is thrown or not.

Example Code

void main() {
  try {
    // do something here
  } catch (e) {
    // print exception
    print(e);
  } finally {
    // always executed
    print('I will always be executed!');
  }
}

Catch Exception

The first argument to the catch is the Exception.

Example Code

This code catches the Exception and prints it out.
void main() {
  print('start');
  try {
    int.parse("mark");
  } catch (ex) {
    print(ex);
  }
  print('finish');
}

Example Code Output

start
FormatException: mark
finish

Catch Exception and Stack Trace

The second argument to the catch is the StackTrace.

Strack Trace

A Stack Trace is a list of the method calls that the application was in the middle of when an Exception was thrown. The most useful information is normally shown at the top of StackTraces, so you should always look at them from the ‘top down’. Sometimes this takes a lot of scrolling up!

Example Code

This code catches the Exception and StackTrace.

void main() {
  print('start');
  try {
    int.parse("mark");
  } catch (ex, stacktrace) {
    print(stacktrace);
  }
  print('finish');
}

Example Code Output

start
FormatException: mark
FormatException: mark
    at Object.wrapException (<anonymous>:370:17)
    at Object.int_parse (<anonymous>:1555:15)
    at main (<anonymous>:1702:11)
    at dartMainRunner (<anonymous>:9:5)
    at <anonymous>:2206:7
    at <anonymous>:2192:7
    at dartProgram (<anonymous>:2203:5)
    at <anonymous>:2210:3
    at replaceJavaScript (https://dartpad.dartlang.org/scripts/frame.html:39:17)
    at https://dartpad.dartlang.org/scripts/frame.html:69:7
finish

Catch Specific Exceptions

If you know you want to catch a specific Exception then you can use an ‘on’ instead of a ‘catch’. Consider leaving a ‘catch’ at the bottom to catch other Exceptions.

You can optionally add the ‘catch(e)’ or catch(e, s)’ after if you want the Exception and StackTrace data as arguments.

Example Code

void main() {
  print('start');
  try {
    int.parse("mark");
  } on FormatException{
    print('invalid string');
  } catch (ex,stacktrace) {
    print(stacktrace);
  }
  print('finish');
}

Example Code Output

start
invalid string
finish

Throw Exception

To throw an Exception simply use the ‘throws’ keyword and instantiate the Exception.

Example Code

throw new TooOldForServiceException();

Rethrow Exception

Once you have caught an Exception, you have the option of rethrowing it so that it bubbles up to the next level.  So, you could catch an Exception, log it then rethrow it so it is dealt with at a higher level.

Example Code

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}
void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Output

misbehave() partially handled JsNoSuchMethodError.
main() finished handling JsNoSuchMethodError.

Create Custom Exceptions

It is very simple to create your own custom Exception.

Simply implement the Exception interface.

Example Code

class TooOldForServiceException implements Exception {
  Cadet _cadet;
 
  TooOldForServiceException(this._cadet);
 
  toString(){
    return "${_cadet.name} is too old to be in military service.";
  }
}
 
class Cadet {
  String _name;
  int _age;
 
  Cadet(this._name, this._age);
 
  get age{
    return _age;
  }
 
  get name{
    return _name;
  }
 
}
 
void main() {
  print('start');
 
  List<Cadet> cadetList = [
    Cadet("Tom", 21),
    Cadet("Dick", 37),
    Cadet("Harry", 51),
    Cadet("Mark", 52),
  ];
 
  List<Cadet> validCadetList = [];
  for (Cadet cadet in cadetList){
    try {
      validateCadet(cadet);
      validCadetList.add(cadet);
    } on TooOldForServiceException catch(ex) {
      print(ex);
    } // .. other validation exceptions ...  
  }
 
  print('finish: ${validCadetList.length} of ${cadetList.length} cadets are valid.');
}
 
void validateCadet(Cadet cadet){
  if (cadet.age > 50){
    throw new TooOldForServiceException(cadet);
  }
  // .. other validations ...
}
 

Example Code Output

start
Harry is too old to be in military service.
Mark is too old to be in military service.
finish: 2 of 4 cadets are valid.