31
votes

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");
            },
          ),
        ),
      ),
    );
  }
}
4
did you by any chance were able to catch unhandled exception at the top level main function?? - Yadu

4 Answers

38
votes

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.

17
votes

Note: Sample code updated for onError deprecated warning by using runZonedGuarded

Hi @james Allen I think all the unhandled error can be catch globally & it can handle or print in console regardless of modes. In your example I think you missed to add this line WidgetsFlutterBinding.ensureInitialized() before setting up flutterError.onError so it works as you except.

To handle the unhandled exceptions in flutter we have get help from these safety wraps up to catch those exception, which are listed below

  • Zone ( to catch all unhandled-asynchronous-errors )
  • FlutterError.onError ( to catch all unhandled-flutter-framework-errors )

ZONE :

zone are not-belong to flutter framework, it's from dart itself. In dart documentation it states that..

zone protecting your app from exiting due to an uncaught exception thrown by asynchronous code

Reference link : https://dart.dev/articles/archive/zones#handling-asynchronous-errors

so by wrapping our app inside zone to our flutter app, helps to catch all the unhandled-asynchronous-errors below its simple code.

Example:

void main() {
  runZonedGuarded(() async {
    runApp(MyApp()); // starting point of app
   },(error, stackTrace) {
      print("Error FROM OUT_SIDE FRAMEWORK ");
      print("--------------------------------");
      print("Error :  $error");
      print("StackTrace :  $stackTrace");
  });
 }

FlutterError.onError :

from official flutter documentation it says,

The Flutter framework catches errors that occur during callbacks triggered by the framework itself, including during build, layout, and paint.

All these errors are routed to the FlutterError.onError handler. By default, this calls FlutterError.dumpErrorToConsole,

Reference link : https://flutter.dev/docs/testing/errors

so by using flutterError.onError we can able to catch all the flutter framework related errors, below its simple example..

Example:

void main() {
    WidgetsFlutterBinding.ensureInitialized(); //imp line need to be added first
    FlutterError.onError = (FlutterErrorDetails details) {
    //this line prints the default flutter gesture caught exception in console
    //FlutterError.dumpErrorToConsole(details);
    print("Error From INSIDE FRAME_WORK");
    print("----------------------");
    print("Error :  ${details.exception}");
    print("StackTrace :  ${details.stack}");
    };
    runApp(MyApp()); // starting point of app
 }

don't forget to add this line WidgetsFlutterBinding.ensureInitialized() first before setting up the flutter framework's error catching helper.

Note:

  1. Flutter red screen to death error will also catch under flutterError.onError catcher.
  2. Flutter red screen will be visible in dev mode by default in production it will be as just plain BG as per flutter documentation.
  3. Flutter screen to death can be customizable as per our creativity.
  4. Flutter wont kill the app, even the exception are not handled by these helpers.
  5. Bonus one - these is also library called catcher in pub.dev which you might take a look for error catching.

combination of these two helper from dart & flutter framework we can catch all the unhandled errors globally, these are all which I understand from the web documentations when I was assigned for global exception handling task in flutter, feel free to correct me if any mistakes.

0
votes

So the 'runZoned' and the 'Flutter.onError' are for 'error' handling in dart and flutter framework respectively. But in your code you are throwing an 'exception'. Which is not handled by the 'run..' or '..onErr'. If you want to see the "Just testing" in the console, modify the throw statement as follows -> throw "Just testing"; and you will see it in the console logs.

0
votes

Make sure you're using WidgetsFlutterBinding.ensureInitialized() like this

runZonedGuarded(() {
  WidgetsFlutterBinding.ensureInitialized();
  FlutterError.onError = (FlutterErrorDetails errorDetails) {
    Utilities.log("Will log here ${errorDetails.exception.toString()}");
  };
  runApp(app);
}, (error, stackTrace) {
  Utilities.log("For other catch ${error.toString()}");
});