Better App Exceptions Catching Than The Flutter SDK
Easier and more robust way to catch app errors than the SDK way.
With using Catcher 2 one can plug into third party services like crash analytics and even plug into slack and discord. And, the benefit of getting a pretty full page exception report with accept and cancel buttons.
First, let me show the SDK way.
The SDK Way Of Catching App Errors
As of Flutter 3.3 we no longer use the Dart Zones construct but use this to catch errors:
import 'package:flutter/material.dart'; | |
import 'dart:ui'; | |
class MyErrorsHandler { | |
Future<void> initialize() async { | |
debugPrint('initialize'); | |
// do some initialization | |
} | |
void onErrorDetails(FlutterErrorDetails details) { | |
debugPrint('onErrorDetails'); | |
debugPrint(details.toString()); | |
} | |
void onError(Object error, StackTrace stack) { | |
debugPrint('onError'); | |
debugPrint(error.toString()); | |
debugPrint(stack.toString()); | |
} | |
} | |
// Since flutter 3.3 or so we no longer | |
// use separate zones to accomplish this as | |
// we are using platform dispatcher instead. | |
Future<void> main() async { | |
await myErrorsHandler.initialize(); | |
FlutterError.onError = (details) { | |
FlutterError.presentError(details); | |
myErrorsHandler.onErrorDetails(details); | |
}; | |
PlatformDispatcher.instance.onError = (error, stack) { | |
myErrorsHandler.onError(error, stack); | |
return true; | |
}; | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
builder: (context, widget) { | |
Widget error = const Text('...rendering error...'); | |
if (widget is Scaffold || widget is Navigator) { | |
error = Scaffold(body: Center(child: error)); | |
} | |
ErrorWidget.builder = (errorDetails) => error; | |
if (widget != null) return widget; | |
throw ('widget is null'); | |
}, | |
); | |
} | |
} |
We have an error handler, and we are using debugPrint which means this only happens in debug builds and as when we do a release build the SDK framework strips out the debugPrint statements and makes them NOOPs.
The FlutterError onError method and the PlatformDispatcher.instance onError method server to catch both errors and framework errors. Then of course we can set up the ErrorWidget also to be triggered if the MaterialApp builder itself produces a rendering error.
The problems with this is:
-No way to use email sending or a third party service
-No way to display a full error details page
A better way is to use the Flutter Community Catcher Package.
Using The Catcher Two Package
The author of the original Catcher package is no longer updating the package, so a flutter community member stepped up and soft forked it into Catcher2 package:
Because I usually set up for observable logging, I set up a Catcher Two Logger:
// Copyright 2023 Fredrick Allan Grott. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
import 'dart:developer'; | |
import 'package:catcher_2/catcher_2.dart'; | |
/// CustomCatcherTwoLogger where the Dart developer log | |
/// method is used to output the correct log message. Have to use | |
/// log instead of print for observable logs as print is stripped | |
/// in release mode. | |
/// | |
/// @author Fredrick Allan Grott. | |
class CustomCatcherTwoLogger extends Catcher2Logger { | |
@override | |
void info(String message) { | |
log('Custom Catcher Logger | Info | $message'); | |
} | |
@override | |
void fine(String message) { | |
log('Custom Catcher Logger | Fine | $message'); | |
} | |
@override | |
void warning(String message) { | |
log('Custom Catcher Logger | Warning | $message'); | |
} | |
@override | |
void severe(String message) { | |
log('Custom Catcher Logger | Severe | $message'); | |
} | |
} |
If you want to use it in non-observable logging form you would use debugPrint as that will be set by the SDK to NOOPs when it is built in release mode.
Next we set up Catcher2 options for each build mode we want to have app exceptions catching. In my case, I never have it used in profile mode, and thus I just have debugOptions and releaseOptions defined.
Note, that localization is defined separately in addition to the main app content. I have defined both English and German in my example.
Now, in the main function, the flutterError and PlatformDispatcher onError method constructs and the runApp function get replaced with the Catcher2 declaration:
// Copyright 2023 Fredrick Allan Grott. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
import 'package:catcher_2/catcher_2.dart'; | |
import 'package:catcher_two_demo/src/app_logging.dart'; | |
import 'package:catcher_two_demo/src/catcher_two_options.dart'; | |
import 'package:catcher_two_demo/src/my_app.dart'; | |
import 'package:catcher_two_demo/src/settings/settings_controller.dart'; | |
import 'package:catcher_two_demo/src/settings/settings_service.dart'; | |
void main() async { | |
// Set up the SettingsController, which will glue user settings to multiple | |
// Flutter Widgets. | |
final settingsController = SettingsController(SettingsService()); | |
// Load the user's preferred theme while the splash screen is displayed. | |
// This prevents a sudden theme change when the app is first displayed. | |
await settingsController.loadSettings(); | |
// init app logging | |
AppLogging(); | |
// Run the app and pass in the SettingsController. The app listens to the | |
// SettingsController for changes, then passes it further down to the | |
// SettingsView. | |
Catcher2( | |
rootWidget: MyApp(settingsController: settingsController), | |
releaseConfig: releaseOptions, | |
debugConfig: debugOptions, | |
); | |
} |
Thoughts
That is the basic set-up to app exceptions catching, which you can then customize for whatever handlers you need.
Fred Grott's Newsletter
Code: Flutter Bytes Hub
Demos-at-YouTube: Fred Grott’s YouTube Channel
An ADHD'ers super focus on Flutter app design and the side hustle of designing flutter apps. Delivered, several days every weekday to your inbox.