Handling Errors and Exceptions
1. Using try-catch Blocks
| Structure | Description | Usage |
|---|---|---|
| try | Code block to monitor for exceptions | Wrap code that might throw an exception |
| catch | Handles exceptions thrown in the try block | Can be used with or without a parameter |
| finally | Optional block that runs regardless of exception | Used for cleanup operations |
Example: Basic try-catch block
try {
int result = 10 ~/ 0;
} catch (e) {
print('An error occurred: $e');
}
Example: Catching specific exceptions
try {
int result = int.parse('not_a_number');
} on FormatException catch (e) {
print('Format error: $e');
} catch (e) {
print('General error: $e');
}
Note: In Dart,
try-catch blocks are used for handling runtime exceptions. Unlike some languages, Dart does not support checked exceptions.
2. Throwing Exceptions (throw keyword)
| Exception Type | Description | Usage |
|---|---|---|
| Exception | Generic exception class in Dart | throw Exception('Something went wrong'); |
| Error | Represents a serious error that usually indicates a bug in the program | throw Error.safeToString('Unexpected error'); |
| FormatException | Thrown when parsing or formatting fails | throw FormatException('Invalid JSON format'); |
| RangeError | Thrown when a value is outside the expected range | throw RangeError('Index out of bounds'); |
Example: Throwing a Custom Exception
class CustomException implements Exception {
final String message;
CustomException(this.message);
@override
String toString() => 'CustomException: $message';
}
void main() {
try {
throw CustomException('This is a custom error');
} catch (e) {
print(e);
}
}
Note: The
throw keyword can be used with any object that implements the Exception interface.
3. Creating Custom Exceptions
| Property | Description | Example |
|---|---|---|
| CustomException class | Base class for custom exceptions in Flutter | class CustomException implements Exception { |
| message | Human-readable error message | final String message; |
| code | Optional error code for categorization | final String? code; |
Example: Defining a Custom Exception
class NetworkException extends Exception {
final String message;
final String? code;
NetworkException(this.message, {this.code});
@override
String toString() {
return 'NetworkException: $message${code != null ? ' (Code: $code)' : ''}';
}
}
Example: Throwing a Custom Exception
try {
// Simulate network error
if (networkError) {
throw NetworkException('Failed to connect to server', code: 'CONNECTION_ERROR');
}
} catch (e) {
if (e is NetworkException) {
print(e.toString());
}
}
Note: Custom exceptions should extend
Exception or Error to be properly handled by Flutter's error handling mechanisms.
4. Using Error Widgets
| Widget | Description | Usage |
|---|---|---|
| ErrorWidget | Displays a fallback UI when an error occurs in the widget tree. | ErrorWidget.builder = (FlutterErrorDetails details) { return MyCustomErrorWidget(); }; |
Example: Custom Error Widget
class MyCustomErrorWidget extends StatelessWidget {
final dynamic error;
const MyCustomErrorWidget({Key? key, required this.error}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text('An error occurred: $error'),
);
}
}
Note:
ErrorWidget is a global widget that catches unhandled errors in the app.
Warning: Avoid throwing errors inside
ErrorWidget as it can lead to infinite loops.
5. Implementing Global Error Handling
Global error handling in Flutter allows you to catch unhandled exceptions and errors across your app, providing a consistent way to report or log issues.
| Method | Description | Usage |
|---|---|---|
| FlutterError.onError | Callback for unhandled errors in the Flutter framework. | FlutterError.onError = (FlutterErrorDetails details) {<br> // Log or report error<br>}; |
| Zone.handleUncaughtError | Used to handle errors in a specific zone, useful for custom error handling logic. | Zone.current.handleUncaughtError = (Object error, StackTrace stack) {<br> // Custom logic<br>}; |
Example: Setting Up Global Error Handler
import 'package:flutter/material.dart';<br><br>void main() {<br> FlutterError.onError = (FlutterErrorDetails details) {<br> // Log the error<br> print('Global Error: ${details.exception}');<br> };<br><br> runApp(MyApp());<br>}
Note:
FlutterError.onError is the most common way to implement global error handling in Flutter apps.
Warning: Ensure that error logging or reporting does not expose sensitive user data.
6. Handling Async Errors
| Error Type | Description | Handling Strategy |
|---|---|---|
| async/await | Errors in async functions can be caught using try/catch blocks. | Wrap async operations in try/catch to handle exceptions gracefully. |
| Future | Errors in Future operations propagate to the next error handler. | Use .catchError() or .whenComplete() to handle errors. |
| Stream | Errors in streams can be handled using .handleError(). |
Use .handleError() or .onErrorResumeNext() for stream errors. |
Example: Handling Async Errors with try/catch
Future<String> fetchData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to load data');
}
} catch (e) {
print('Error occurred: $e');
return 'Default data';
}
}
Example: Handling Future Errors
Future<String> fetchWithFuture() {
return http.get(Uri.parse('https://api.example.com/data'))
.then((response) {
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to load data');
}
})
.catchError((error) {
print('Caught error: $error');
return 'Default data';
});
}
Note: When using
async/await, errors in nested async calls must be handled explicitly to prevent uncaught exceptions.
7. Using Result/Either Pattern
The Result/Either pattern is a functional programming concept used to handle operations that might fail. In Flutter, it's commonly implemented using a custom Result class or by leveraging libraries like dartz or fpdart.
| Property | Description | Usage |
|---|---|---|
| success | Represents a successful operation with a value. | Result.success(data) |
| failure | Represents a failed operation with an error. | Result.failure(error) |
Example: Basic Result Usage
class Result<T> {
final bool isSuccess;
final T? value;
final String? error;
Result.success(this.value) : isSuccess = true, error = null;
Result.failure(this.error) : isSuccess = false, value = null;
}
Future<Result<String>> fetchData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
return Result.success(response.body);
} catch (e) {
return Result.failure('Failed to fetch data: $e');
}
}
Example: Using Result in a Widget
class DataWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<Result<String>>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
final result = snapshot.data!;
return result.isSuccess
? Text(result.value!)
: Text('Error: ${result.error}');
}
return Text('No data');
},
);
}
}
Note: The Result pattern helps in explicitly handling success and failure cases, making error handling more predictable and less prone to runtime exceptions.
8. Logging Errors
| Method | Description | Usage |
|---|---|---|
| Logger | Core logging class in Flutter | final logger = Logger(); |
| logger.d() | Debug level logging | logger.d('Debug message'); |
| logger.i() | Info level logging | logger.i('Info message'); |
| logger.w() | Warning level logging | logger.w('Warning message'); |
| logger.e() | Error level logging | logger.e('Error message'); |
| logger.f() | Fatal level logging | logger.f('Fatal error'); |
Example: Basic Error Logging
import 'package:logger/logger.dart';
void main() {
final logger = Logger();
try {
throw Exception('Something went wrong');
} catch (e) {
logger.e('Caught exception: $e');
}
}
Example: Logging with Context
import 'package:logger/logger.dart';
final logger = Logger();
void logErrorWithContext(String operation, dynamic error) {
logger.e(
'Operation failed: $operation',
error,
StackTrace.current,
);
}
void main() {
logErrorWithContext('Database query', 'Connection timeout');
}
Note: The
logger package is not part of Flutter's core, so it must be added to pubspec.yaml.
Warning: Avoid logging sensitive data like passwords or tokens.
9. Integrating Crash Reporting
Integrating crash reporting in Flutter helps developers identify and resolve issues in production apps. Several services provide robust crash reporting solutions tailored for mobile platforms.
| Service | Description | Key Features | Support |
|---|---|---|---|
| Firebase Crashlytics | Part of Firebase, provides real-time crash reporting and analytics. |
|
iOS, Android |
| Sentry | Open-source error tracking platform with extensive integrations. |
|
iOS, Android, Web |
| Bugsnag | Mobile-first crash reporting with deep integration capabilities. |
|
iOS, Android |
Example: Setting up Firebase Crashlytics in Flutter
// pubspec.yaml
dependencies:
firebase_core: ^2.15.0
firebase_crashlytics: ^3.0.15
// main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}
Note: Ensure that Crashlytics is initialized before any potential errors occur to capture all exceptions.
Warning: Crash reports may contain sensitive user data. Review and sanitize logs before sending to third-party services.
10. Displaying User-Friendly Error Messages
| Property | Description | Example |
|---|---|---|
| ErrorWidget.builder | Customizes how errors are displayed in the UI | |
Example: Showing a Custom Error UI
class CustomErrorWidget extends StatelessWidget {
final String message;
const CustomErrorWidget({Key? key, required this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red),
Text(message, style: TextStyle(fontSize: 18)),
],
),
);
}
}
Note: Use
ErrorWidget.builder to globally customize error displays in debug mode.
Warning: Avoid showing raw exception details to users in production builds.