Bug Report

However, we’ve gotten ahead of ourselves a little bit. Let’s go back and take a look at what’s first passed to the ErrorWidget.builder as a parameter. What’s passed comes from the private function, _debugReportException(). In the screenshot below, notice there are three positional parameters and one named parameter called, informationCollector, passed to the function, _debugReportException().

The first parameter is the function, ErrorDescription(), and it returns a DiagnosticsNode object describing what was happening when the error occurred (Note, it provides more information when in Development mode than when in Release mode.). The next two parameters are an Error object and a StackTrace object. The ‘error’ object could be anything frankly — however, in many cases, it’ll either be an AssertionError object or a FlutterError object. Next, the StackTrace object lists the call sequence leading up to the error — conveying all the functions and class objects involved. It’s so to hopefully assist developers in correcting the issue.

The last parameter, the named parameter called, informationCollector, and it does just that. It collects even more information regarding the error. It’s, in fact, an Iterable listing of DiagnosticsNode’s. It’s an expensive process ( a lot of memory cycles involved) and so the synchronous generator, sync*, is used. It ‘lazily’ produces a sequence of values one at a time into the Iterable object.

The Details

Let’s now take a peek inside the function, _debugReportException(). At a glance, we see this function returns an object of type, FlutterErrorDetails, to the static function, ErrorWidget.builder. Note, ErrorWidget.builder is of type:

Widget Function(FlutterErrorDetails details);

It returns a widget replacing the one that failed to build because of an error. But again, I’m getting a little ahead of myself. Back to the function, _debugReportException(), and we see after producing the ‘exception’ object, FlutterErrorDetails, it calls the static function, reportError, from the error class, FlutterError. See below.

Report The Error

Let’s take a look that this static function, reportError. You can see, with its assert statements, it insists it is passed a FlutterErrorDetails object with a non-null exception. It then checks if the static function of type, FlutterExceptionHandler, exists, and if so, calls it with the FlutterErrorDetails object as a parameter. Note, that if statement tells you you could conceivably set that static function, FlutterError.onError, to null! This means you could literally ignore any errors in your app! Yeah, don’t do that.

So, to review, you’ll find throughout the Flutter framework that the static function, FlutterError.onError, is called immediately prior to the static function, ErrorWidget.builder, with both using the same ‘exception’ object, FlutterErrorDetails. Pretty consistent too.

Dump The Error

Again, by design, the static function, FlutterError.onError, is called whenever the Flutter framework catches an error. Its default behavior is to then call yet another static function, dumpErrorToConsole. Now guess what that does.

The big thing to come away with this, however, is that it is here where you could set your own function and override this default behavior. See where I’m going here? You can define your own ‘error handling’ by assigning a different ‘voidCallback’ function to the static function, Flutter.onError, of the type:

void Function(FlutterErrorDetails details)

Note, it’s said if the error handler itself throws an exception, it will not be caught by the Flutter framework. There is a means to catch errors in the error handler. We’ll talk about that soon. For now, below you can see in the Flutter framework, the Flutter’s error handler is indeed assigned as default to dump errors to your IDE’s console in development and to your phone’s log files in production.

Dump The First Not The Rest

We’ll take a quick look at that static function, dumpErrorToConsole. Note, with its static variable, _errorCount, it only records in detail the ‘first’ error to the console. Any further errors are usually given just a one-line summary only.

The Console Knows

And so, with a press of that ‘+’ button, the following is recorded in the phone logs and depicted on the IDE’s console. It displays the exception message, however, it also gives you a link to the possible location of the error as well as a stack trace. All in an effort to correct the issue while in development.

With that, your app is terminated, and it won’t continue. In production, the user is greeted with a grey screen leaving them to hit the ‘back button’ on their phone, for example, to retreat back to the phone’s home screen. Done. Note, you can quickly recap the process in Handling errors in Flutter.

“If the exception isn’t caught, the isolate that raised the exception is suspended, and typically the isolate and its program are terminated.” — Exceptions

To Catch An Error

Now let’s slap in Jakub’s Catcher and see what happens now when we again press the ‘+’ button in our simple app example. His readme on pub.dev does a good job describing how to implement Catcher into your app. The screenshots below show how to do that as well as what happens when the ‘+’ button is finally pressed.