Exceptions and Error Handling

18.1 Throwing and Catching

double divide(int a, int b) {
  if (b == 0) throw ArgumentError('Divisor cannot be zero');
  return a / b;
}

void main() {
  try {
    divide(10, 0);
  } on ArgumentError catch (e) {          // catch a specific type
    print('Bad argument: ${e.message}');
  } on FormatException {
    print('Bad format');
  } catch (e, stackTrace) {               // any other object, plus the stack trace
    print('Unexpected: $e\n$stackTrace');
    rethrow;                              // propagate while preserving the stack trace
  } finally {
    print('Always runs');
  }
}

18.2 Exception vs Error

Expected, recoverable failures (I/O, parsing, network) Programming bugs (shouldn't be caught normally)

FormatException, IOException, HttpException, TimeoutException

ArgumentError, RangeError, StateError, TypeError, UnimplementedError, UnsupportedError, LateInitializationError, ConcurrentModificationError Usually no (fix the bug)

18.3 Custom Exceptions

class InsufficientFundsException implements Exception {
  final double shortfall;
  const InsufficientFundsException(this.shortfall);
  @override
  String toString() => 'InsufficientFundsException: short by \$${shortfall.toStringAsFixed(2)}';
}

sealed class AppException implements Exception {
  const AppException(this.message);
  final String message;
}
final class NetworkException extends AppException { const NetworkException(super.message); }
final class NotFoundException extends AppException { const NotFoundException(super.message); }

void withdraw(double amount, double balance) {
  if (amount > balance) throw InsufficientFundsException(amount - balance);
}

18.4 Errors Without Exceptions: Result Types

Libraries: fpdart (Either, Option, TaskEither), result_dart, dartz, multiple_result.

sealed class Result<T> {
  const Result();
}
final class Ok<T> extends Result<T> { const Ok(this.value); final T value; }
final class Err<T> extends Result<T> { const Err(this.error); final Exception error; }

Result<int> parseAge(String input) {
  final n = int.tryParse(input);
  if (n == null) return Err(FormatException('Not a number: $input'));
  if (n < 0) return Err(FormatException('Age cannot be negative: $n'));
  return Ok(n);
}

switch (parseAge('42')) {
  case Ok(:final value): print('Age $value');
  case Err(:final error): print('Error: $error');
}

18.5 Error Handling Best Practices

Throw Exception subtypes for recoverable conditions and Errors for bugs. Use on Type catch for specific handling.

Don't swallow exceptions silently. Use rethrow to propagate. Use Error.throwWithStackTrace to rethrow with an original trace.

Async errors: see Module 25 (uncaught Future errors and zones).