Flutter provides powerful mechanisms to handle errors gracefully, ensuring a smooth user experience even when unexpected issues arise. This article explores how to use runZonedGuarded
and global error handling techniques in a Flutter application. 🌟
The given Flutter application uses:
A GlobalKey
to manage navigation and error screens.
FlutterError.onError
to catch framework errors.
runZonedGuarded
to capture errors occurring outside the Flutter framework.
A dedicated ErrorScreen
to display unexpected error messages.
import 'dart:async';
import 'package:flutter/material.dart';
final GlobalKey navigatorKey = GlobalKey();
void main() {
// Handling Flutter framework errors
FlutterError.onError = (FlutterErrorDetails details) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (navigatorKey.currentState?.overlay?.context.widget is! ErrorScreen) {
navigatorKey.currentState?.pushReplacement(
MaterialPageRoute(
builder: (context) => ErrorScreen(errorDetails: details),
),
);
}
});
};
// Handling all uncaught errors
runZonedGuarded(() => runApp(MyApp()), (error, stackTrace) {
debugPrint("Caught error in runZonedGuarded: $error");
});
}
runZonedGuarded
The function runZonedGuarded
provides an execution zone where any uncaught runtime exceptions are intercepted, allowing developers to log or handle errors effectively.
runZonedGuarded(
() => runApp(MyApp()),
(error, stackTrace) {
debugPrint("Error caught: $error");
}
);
runZonedGuarded
? Captures errors outside the Flutter framework – If an error occurs in asynchronous tasks like network requests or database operations, runZonedGuarded
ensures it doesn’t crash the app silently.
Prevents app crashes – Instead of the app terminating unexpectedly, errors can be logged or redirected to an error reporting tool.
Flutter provides FlutterError.onError
to catch exceptions occurring within the Flutter widget tree.
FlutterError.onError = (FlutterErrorDetails details) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (navigatorKey.currentState?.overlay?.context.widget is! ErrorScreen) {
navigatorKey.currentState?.pushReplacement(
MaterialPageRoute(
builder: (context) => ErrorScreen(errorDetails: details),
),
);
}
});
};
The function captures Flutter framework errors.
A new ErrorScreen
is displayed, replacing the current screen, so users don’t experience app crashes.
WidgetsBinding.instance.addPostFrameCallback
ensures the navigation occurs after the current frame renders.
The NewScreen
widget contains a forced error:
class NewScreen extends StatelessWidget {
const NewScreen({super.key, required this.x});
final int? x;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('New Screen')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('${x! + 10}'),
),
),
);
}
}
Here, x
is explicitly null
, and x! + 10
will throw an error when tapped. Since this error is in the widget tree, FlutterError.onError
will catch it and navigate to ErrorScreen
.
class ErrorScreen extends StatelessWidget {
final FlutterErrorDetails errorDetails;
const ErrorScreen({required this.errorDetails, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Oops! Something went wrong")),
body: Center(
child: Text(errorDetails.exceptionAsString(), textAlign: TextAlign.center),
),
);
}
}
This screen provides a user-friendly message instead of crashing the app.
Using runZonedGuarded
and FlutterError.onError
ensures:
Errors do not lead to unexpected crashes.
Issues are logged and can be displayed to users.
The app remains stable, improving the overall user experience.
By implementing these techniques, developers can handle unexpected failures efficiently, making their Flutter apps more resilient and reliable.