How to catch async exception in one place (like main) and show it in AlertDialog?
By default, if there is an uncaught exception in a Flutter application, it is passed to FlutterError.onError
. This can be overridden with a void Function(FlutterErrorDetails)
to provide custom error handling behaviour:
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
print(details.exception); // the uncaught exception
print(details.stack) // the stack trace at the time
}
runApp(MyApp());
}
If you want to show a dialog in this code, you will need access to a BuildContext
(or some equivalent mechanism to hook into the element tree).
The standard way of doing this is with a GlobalKey
. Here, for convenience (because you want to show a dialog) you can use a GlobalKey<NavigatorState>
:
void main() {
WidgetsFlutterBinding.ensureInitialized();
final navigator = GlobalKey<NavigatorState>();
FlutterError.onError = (details) {
navigator.currentState!.push(MaterialPageRoute(builder: (context) {
// standard build method, return your dialog widget
return SimpleDialog(children: [Text(details.exception.toString())]);
}));
}
runApp(MyApp());
}
Note that if you need a BuildContext
inside your onError
callback, you can also use navigator.currentContext!
.
You then need to pass your GlobalKey<NavigatorState>
to MaterialApp
(or Navigator
if you create it manually):
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey, // pass in your navigator key
// other fields
);
}
cheiser
Updated on December 30, 2022Comments
-
cheiser over 1 year
Trouble
I build Flutter app + Dart. Now i am trying to catch all future exceptions in ONE place (class) AND show
AlertDialog
.Flutter Docs proposes 3 solutions to catch
async
errors:- runZonedGuarded
- ... async{ await future() }catch(e){ ... }
- Future.onError
But no one can achieve all of the goals (in its purest form):
First: can't run in widget's
build
(need to returnWidget
, but returnsWidget?
.Second: works in
build
, but don't catch async errors, which were throwed by unawaited futures, and is"dirty" (forces to useWidgetBinding.instance.addPostFrameCallback
. I can ensure awaiting futures (which adds to the hassle), but I can't check does ensures it third-part libraries. Thus, it is bad case.Third: is similar to second. And looks monstrous.
My (bearable) solution
I get first solution and added some details. So,
I created
ZonedCatcher
, which showsAlertDialog
with exception or accumulates exceptions if it doesn't know where to showAlertDialog
(BuildContext
has not been provided).AlertDialog
requiresMaterialLocalizations
, soBuildContext
is taken fromMaterialApp
's childMaterialChild
.void main() { ZonedCatcher().runZonedApp(); } ... class ZonedCatcher { BuildContext? _materialContext; set materialContext(BuildContext context) { _materialContext = context; if (_exceptionsStack.isNotEmpty) _showStacked(); } final List<Object> _exceptionsStack = []; void runZonedApp() { runZonedGuarded<void>( () => runApp( Application( MaterialChild(this), ), ), _onError, ); } void _onError(Object exception, _) { if (_materialContext == null) { _exceptionsStack.add(exception); } else { _showException(exception); } } void _showException(Object exception) { print(exception); showDialog( context: _materialContext!, builder: (newContext) => ExceptionAlertDialog(newContext), ); } void _showStacked() { for (var exception in _exceptionsStack) { _showException(exception); } } } ... class MaterialChild extends StatelessWidget { MaterialChild(this.zonedCatcher); final ZonedCatcher zonedCatcher; @override Widget build(BuildContext context) { zonedCatcher.materialContext = context; //! ... } }
flaws
- At this moment I don't know how organize app with several pages.
materialContext
can be taken only fromMaterialApp
childs, but pages are set already at theMaterialApp
widget. Maybe, I will injectZonedCatcher
in all pages and building pages will re-setmaterialContext
. But I probably will face withGlobalKey
's problems, like resetingmaterialContext
by some pages at the same time on gestures. - It is not common pattern, I have to thoroughly document this moment and this solution makes project harder to understand by others programmists.
- This solution is not foreseen by Flutter creators and it can break on new packages with breaking-changes.
Any ideas?
-
CrimsonFoot almost 3 yearsI don't know why you try to simplify catching exceptions too much but I think if you use GetX package, you can show dialog without context. I don't think this is a smooth solution but it seems to work
-
LearnFlutter about 2 yearsIm having a similiar issue here stackoverflow.com/questions/71110229/…