๐ก๏ธ Lesson 6: Error Handling
Why Error Handling Matters
๐ง Without error handling, any failure crashes your entire application
Users lose data, trust evaporates, and debugging becomes a nightmare. Proper error handling keeps your app running even when things go wrong, and provides clear feedback about what happened.
โ Without Handling
// User types invalid number
int age = int.parse('twenty');
// ๐ฅ APP CRASHES โ FormatException
// User sees nothing but a crash
๐ฐ Data lost, user frustrated, app appears broken
โ With Handling
try {
int age = int.parse(input);
} catch (e) {
print('Please enter a valid number');
}
// โ
App stays alive, user sees helpful message
๐ App survives, user gets helpful guidance, trust maintained
Errors vs Exceptions โ The Critical Distinction
๐ง Dart distinguishes between two types of problems:
Understanding which is which determines whether you should fix your code or handle the failure gracefully.
๐ด Errors (Programmer Mistakes)
These indicate bugs that should not be caught. They mean your code is wrong and needs to be fixed.
- TypeError โ calling a method on null
- AssertionError โ assert condition fails
- RangeError โ index out of bounds
- NoSuchMethodError โ calling undefined method
Rule: DON'T catch errors. Fix the bug instead.
๐ก Exceptions (Expected Failures)
These are recoverable situations that should be caught and handled. They're not bugsโthey're expected possibilities.
- FormatException โ invalid data format
- IOException โ file or network failure
- TimeoutException โ operation timed out
- HttpException โ bad HTTP response
Rule: ALWAYS catch and handle exceptions.
๐ฎ Classify These Scenarios
var x; print(x.length);
json.decode('not json');
http.get() after 30 seconds
list[-1];
Try/Catch/Finally โ The Safety Net
๐ง The try/catch/finally structure catches exceptions and keeps your app alive
Think of it as a safety net under a trapeze artist. The try block is the performance, catch catches the fall, and finally cleans up regardless of what happened.
Complete Pattern with Line-by-Line Explanation
try {
// Code that might throw an exception
final file = File('data.txt');
final contents = await file.readAsString();
print('File loaded: \$contents');
} on FileSystemException catch (e) {
// Catches specific file-related exceptions
print('Could not read file: \${e.message}');
} on FormatException catch (e) {
// Catches data format problems
print('File contained invalid data: \$e');
} catch (e, stackTrace) {
// Catches ALL other exceptions
print('Unexpected error: \$e');
print('Stack trace: \$stackTrace');
} finally {
// ALWAYS runs โ perfect for cleanup
print('Cleanup: closing resources');
}
๐ Structure Breakdown
try wraps risky codeon FileSystemException catches specific typeon FormatException catches another typecatch (e, stackTrace) catches everything elsestackTrace shows where error occurredfinally always runs, even after return๐ฏ Click Each Block to Learn Its Purpose:
โ๏ธ Try It: Handle Different Error Types
Creating Custom Exceptions
๐ง When built-in exceptions aren't descriptive enough, create your own
Custom exceptions make your code more readable and help consumers understand exactly what went wrong. Implement Exception and override toString() for clear error messages.
๐๏ธ Build Your Exception
๐ Generated Code
class ValidationException implements Exception {
final String message;
final String field;
final dynamic value;
ValidationException({
required this.message,
required this.field,
this.value,
});
@override
String toString() {
return 'ValidationException: \$message (field: \$field)';
}
}
Using Your Custom Exception
// Throwing your custom exception
void validateEmail(String email) {
if (!email.contains('@')) {
throw ValidationException(
message: 'Invalid email format',
field: 'email',
value: email,
);
}
}
// Catching it specifically
try {
validateEmail('not-an-email');
} on ValidationException catch (e) {
print('Validation failed: \$e');
// Validation failed: ValidationException: Invalid email format (field: email)
}
Throw vs Rethrow โ Propagating Errors
๐ง Throw creates a new exception. Rethrow preserves the original.
Use throw to raise a new exception with a fresh stack trace. Use rethrow (without arguments) inside a catch block to preserve the original stack traceโcrucial for debugging.
๐ Call Stack
main() โ starts the app โ processOrder() โ validatePayment() โ ๐ฅ EXCEPTION HERE๐ Explanation
๐ผ throw (New Exception)
try {
riskyOperation();
} catch (e) {
// Creates NEW exception โ loses original trace!
throw Exception('Something went wrong');
}
โ ๏ธ Stack trace starts from here, original location lost!
๐ rethrow (Preserve Original)
try {
riskyOperation();
} catch (e) {
print('Logging error before rethrowing');
rethrow; // Preserves original stack trace!
}
โ Stack trace preserved โ shows exactly where error originated!
Real-World Error Handling Patterns
๐ง These are the patterns you'll use in production code
๐ Safe File Reading
Handle missing files gracefully
๐ Network with Retry
Retry failed requests with backoff
๐ Safe JSON Parsing
Handle malformed data
๐ User Input Validation
Validate and provide feedback
โข Catch specific exceptions first (
on FormatException), then general (catch)โข Always include finally for resource cleanup (files, network connections)
โข Never swallow exceptions silently โ at minimum, log them
โข Use rethrow when you need to log but can't fully handle the error
โข Create custom exceptions for domain-specific errors
๐ฏ Error Handling Quiz
Score: 0/5 | Hints: 3
Loading question...
Practice Projects
Apply what you've learned by building these hands-on projects.