Flutter catching all unhandled exceptions

dartexceptionflutter

I'm trying to catch all unhandled exceptions in a Flutter app so I can sent it to a crash reporter. There are instructions on how to do this in the Flutter docs. I followed those, and added two bits of code to my app to catch exceptions:

Catch Dart errors by wrapping runApp in runZoned:

runZoned<Future<void>>(
  () async {
    runApp(MyApp());
  },
  onError: (dynamic error, StackTrace stackTrace) {
    print("=================== CAUGHT DART ERROR");
    // Send report
  },
);

Catch flutter errors by setting FlutterError.onError:

FlutterError.onError = (FlutterErrorDetails details) {
  print("=================== CAUGHT FLUTTER ERROR");
  // Send report
};

However, when I test this at runtime by throwing an exception from a button:

throw Exception("Just testing");

The exception appears in the console:

════════ Exception Caught By gesture
═══════════════════════════════════════════════════════════════
The following _Exception was thrown while handling a gesture: Exception:
Just testing When the exception was
thrown, this was the stack:

… etc

But I see no sign of my print statements (CAUGHT DART ERROR or CAUGHT FLUTTER ERROR), and setting breakpoints on those lines never seems to hit, so I think my exception handling code isn't catching it. Am I missing something?

Here's a minimal reproducible example (click the button, which throws an exception, but it's not caught as expected):

import 'dart:async';
import 'package:flutter/material.dart';

void main() =>
  runZoned<Future<void>>(
    () async {
      runApp(MyApp());
    },
    onError: (dynamic error, StackTrace stackTrace) {
      print("=================== CAUGHT DART ERROR");
      // Send report
      // NEVER REACHES HERE - WHY?
    },
  );

class MyApp extends StatefulWidget {
  // This widget is the root of your application.
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  void initState() {
    super.initState();

    // This captures errors reported by the FLUTTER framework.
    FlutterError.onError = (FlutterErrorDetails details) {
      print("=================== CAUGHT FLUTTER ERROR");
      // Send report
      // NEVER REACHES HERE - WHY?
    };
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: RaisedButton(
            child: Text("Throw exception"),
            onPressed: () {
              throw Exception("This is a test exception");
            },
          ),
        ),
      ),
    );
  }
}

Best Answer

Okay I figured out what's going on. Had a look at some related Flutter issues:

flutter tool is too aggressive about catching exceptions

Make hot mode a little less aggressive about catching errors

Break on "unhandled" exceptions when a debugger is attached

It looks like when in debug mode, the flutter framework catches a lot of exceptions, prints to the console (and sometimes shows in the UI itself in red and yellow), but doesn't re-throw - so they are effectively swallowed and there's no way for your own code to catch them. But, when you deploy in release mode, this doesn't happen. So my minimal reproducible example does catch exceptions when built in release mode.