Skip to main content

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>&nbsp;&nbsp;FlutterError.onError = (FlutterErrorDetails details) {<br>&nbsp;&nbsp;&nbsp;&nbsp;// Log the error<br>&nbsp;&nbsp;&nbsp;&nbsp;print('Global Error: ${details.exception}');<br>&nbsp;&nbsp;};<br><br>&nbsp;&nbsp; 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.
  • Automatic crash detection
  • Symbolication support
  • Custom logging
iOS, Android
Sentry Open-source error tracking platform with extensive integrations.
  • Real-time alerts
  • Performance monitoring
  • Custom tags and breadcrumbs
iOS, Android, Web
Bugsnag Mobile-first crash reporting with deep integration capabilities.
  • Crash grouping
  • Contextual data capture
  • Automated error resolution
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
ErrorWidget.builder = (ErrorDetails details) {
  return Center(
    child: Text('Something went wrong!'),
  );
};

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.

Was this article helpful?