59

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");
            },
          ),
        ),
      ),
    );
  }
}
Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
James Allen
  • 6,406
  • 8
  • 50
  • 83
  • 1
    did you by any chance were able to catch unhandled exception at the top level main function?? – Yadu Dec 11 '20 at 05:37

4 Answers4

48

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.

James Allen
  • 6,406
  • 8
  • 50
  • 83
37

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.

SaravanaRaja
  • 3,228
  • 2
  • 21
  • 29
  • Hi, @SaravanaRaja, Thanks for the explanation. It helped me to understand better. Is it possible to give one example test error which is captured in runZonedGuarded block? – Raghu Mudem Sep 16 '21 at 13:06
  • Hi, @SaravanaRaja. I tried runZoned or FlutterError.onError to catch Unhandled Exception for whole app in case wrong type casting but it doesn't work. How can I do? I tried catch (e) and throw a FlutterError or FlutterErrorDetails but it's the same. – Quyen Anh Nguyen Apr 25 '23 at 16:44
10

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

runZonedGuarded(() {
  WidgetsFlutterBinding.ensureInitialized(); //<= the key is here
  FlutterError.onError = (FlutterErrorDetails errorDetails) {
    Utilities.log("Will log here ${errorDetails.exception.toString()}");
  };
  runApp(app);
}, (error, stackTrace) {
  Utilities.log("Others catching ${error.toString()}");
});
Duy Tran
  • 356
  • 3
  • 6
0

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.

Braj
  • 588
  • 3
  • 15