DEV Community

Greg Perry
Greg Perry

Posted on • Originally published at Medium on

Error Handling in Flutter

How to handle errors in Flutter

Handle your errors! Your app will have errors. You’ve got to handle them. Who wants to see the ‘Red Screen of Death?’ Certainly not your users. That’s bad. If it’s going to fail with an unrecoverable error, your app should be proactive about it. If it must crash, your app should make the effort to do so gracefully. It should even demonstrate some resilience and not crash too badly — not lose any data, etc. Your app should also be accountable — telling the user what just happened, and not just leaving them staring at a red screen. Like documentation, error handling seems to be the last thing we developers think about when developing software. That’s not good.

Exceptions are considered conditions that you can plan ahead for and catch. Errors are conditions that you don’t expect or plan for.

A tour of the core libraries

This article will cover Flutter’s error handling. We’ll walk through errors you will likely encounter when developing, and what Flutter does in response. Finally, we’ll cover how to incorporate your own error handling.

Screenshots! Not Gists!

As always, I prefer using screenshots over gists to show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on them to see the code in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers; not on our phones. For now.

No Moving Pictures No Social Media

Note, there will be gif files in this article demonstrating aspects of the topic at hand. However, it’s said viewing such gif files is not possible when reading this article on platforms like Instagram, Facebook, etc. Please, be aware of this and maybe read this article on medium.com.

Let’s begin.

Other Stories by Greg Perry

Catcher in the Flutter

When I started using Flutter, one of the first things I researched was how it performed its error handling — knowing its importance. Doing so led me to the package, Catcher. Its author, Jakub Homlala, wrote a wonderful Dart package offering developers error handling for their apps. It presented developers with a list of pre-defined routines that would fire if and when an unrecoverable error occurred. Further, these options could vary depending upon whether your app was running in development or was running in production. It’s a very good Dart package. It’s certainly better than nothing!

Catcher logo

When In Error

We’ll come back to Jakub’s creation again, but for now, let’s step back and demonstrate what happens when Flutter encounters an error. We’ll use the ol’ standby example I’ve used in many of my past articles — the counter starter app created by the ‘flutter create’ command. In your favorite IDE, when creating a new Flutter project, you’re likely presented with this ‘counter app’ as a starting point. It’s part of that app you see now below.

counter_app_errors.dart

You can see I’m ‘throwing’ an error when the “+” button is pressed. I know it’s not very imaginative, but you must admit, it’s such a simple app — there’s not a lot to work with here. Besides, it’s not the app we’re concerned with; it’s what Flutter does next in response to this blatant error.

Pressing that button initiates a ‘rebuild.’ The setState () function is called which means the build () function in the associating State object will also be called soon after. You can see, in the green inset below, that the state object is indeed called. Notice in the bigger screenshot the call to ‘build’ is in a try-catch statement. You’re going to see try-catch statements throughout the Flutter framework. You see, the framework does its best to anticipate where possible errors could occur. In this case, when an error does occur trying to build a widget, that error is caught in the try-catch statement, and the static function, ErrorWidget.builder, is then called.

framework.dart

As stated in the ErrorWidget’s comments, “when an error occurs while building a widget, the broken widget is replaced by the widget returned by this function.” The two screenshots below present this very function and the ‘red screen of you-know-what’ it returns. Tap on the screenshot, and you’ll be presented with the actual code in the Flutter framework.

framework.dart

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 StackTraceobject. 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.

framework.dart

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);
Enter fullscreen mode Exit fullscreen mode

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.

framework.dart

Report The Error

Let’s take a look at 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.

assertions.dart

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)
Enter fullscreen mode Exit fullscreen mode

Note, that 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.

assertions.dart

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.

assertions.dart

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 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 that red screen leaving them to hit the ‘back button’ on their phone, for example, to retreat back to the phone’s home screen. Done. 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.

counter_app_errors.dart

Behind the scenes, an elaborate report is collected in an instance variable: List _cachedReports. Catcher also presents you with a nice dialog window and a ‘non-red’ screen. There’s a typo on the screen, but that’s alright. As far as I know, English’s not Jakub’s first language. He’s from Poland, I believe. Further, he’s introduced Localization to Catcher, and so you’d be putting in your own messages anyway.

Yes, the Catcher presents you with a nice ‘non-red’ screen. However, Jakub did make that a separate option so that you can keep using the ‘red screen’ instead. I think I know why he did that. You see, he knows when a crash occurs, your app is likely now in an ‘unstable state’ all around. Who knows, displaying such a nice screen using Flutter’s standard widgets could cause a cascade of other errors. You see, standard widgets aren’t used when the static function, ErrorWidget.build, returns that red screen. I’ll explain that later too.

“It is highly recommended that the widget returned from this function perform the least amount of work possible.” — builder property

First, let’s see what was done to ‘replace’ the default error handling. A quick glance at the heart of the Catcher package, and you’ll see how the custom error handling is introduced. It’s done in the function, _setupErrorHooks (). Catcher defines what happens when there’s an error in the Flutter framework, what happens if there’s in the main () entry function, and even what happens if there’s an error in the ‘error handler’ itself.

The first thing we see is the old static function, Flutter.onError. It’s being assigned a new routine that calls the private function, _reportError (). Right off the hop, you’ve got a custom error handler that will catch any errors that may occur in the Flutter framework itself.

The command, Isolate.current.addErrorListener (), is then called to catch any errors that may occur within the entry function, main () — in the ‘root zone’ where all Dart programs start. While the last function, runZoned (), is used to catch any errors in Dart code running ‘outside’ the Flutter framework — like Dart code inside the error handler itself for example.

catcher.dart *

Build Widget Errors

Again, Catcher gives you the option to define a custom ‘error widget’ that would be displayed instead of the widget that fails to build. As you’ve seen, I assigned that option to the simple example app. See below.

You can also see Catcher then takes in the Flutter app itself as a parameter so to assign it to its own separate memory thread (Isolate) as well as set up its error handling. The additional parameters determine what sort of error handling is performed while in development and what is performed while in production. Very nice.

counter_app_errors.dart

And so, when we take a look inside the function, addDefaultErrorWidget, we see Catcher simply assigns an appropriate function to the static function, ErrorWidget.builder, to return a custom widget if a build fails.

catcher.dart *

Android Studio Also Handles Errors

While testing the Catcher error handler, for example, you may be perplexed as to why it is not working at first. If you’re using Android Studio, your IDE may be catching the errors instead. Be sure to go to Settings > Flutter and uncheck the option, ‘Show structured errors for Flutter framework issues’, before working on your error handling. Personally, it took me hours to discover that was the problem.

Error by a Sliver

Let’s take a look at another common situation where errors may occur. It’s when you're making a scrolling ListView — a popular feature in all mobile apps. The tutorial, Write your first Flutter app, has a perfect example with its ‘Startup Name Generator’ app. Its source code is on Github. Of course, you could just tap the screenshot below for a copy.

Your_First_App.dart

The SliverChildBuilderDelegate object is called to build the children for the listing (see below). Each index references a separate item to be listed in the viewport (the visible portion of the listing). Note, I’ve introduced an error to this sample app. It’s to be triggered at startup. However, because the making of this list is enclosed in a try-catch statement, the error is caught, and a widget is instead created from the private function, _createErrorWidget (). You can see this private function is explicitly passed an Exception object and a StackTrace object.

sliver.dart

Inside the function, _createErrorWidget (), you see both of our ‘Error Handlers’ are called one after the other (see screenshots below). Both are supplied the same FlutterErrorDetails object given the context description, ‘building.’ Pretty straightforward. And so, that’s the structure found throughout the Flutter framework, and consequently, the sequence of events if an error occurs:

An error-prone operation is called.

It’s inside a
try-catch statement.

An error calls a function in the
catch clause.

A FlutterErrorDetails object is created.

The FlutterError.reportError() is called.

The FlutterExceptionHandler, onError, is called.

Finally, the ErrorWidget.builder() is called.

Below, you see the ‘red screen’ is the result of this particular error.

sliver.dart

By the way, it’s the RenderErrorBox class that’s used to create that wonder ‘Red Screen of Doom.’ Intentionally, there’s really not much to that class as it paints the error message using low-level functions. This is so to avoid depending on any widgets that may be in an ‘unstable state’ — after all, this class is called when the app has crashed and is likely unstable, so reporting the error should not create any more errors. The screenshot below is the portion of the RenderErrorBox class that defines its background color. Note, that it’s red when in development, but is a light gray color when in production.

error.dart

Under the Flutter.dev Cookbook, there are sections describing some of the error handling we’ve reviewed today. I’ve provided links here for your future reference: Create a function to report errors and Catch and report Dart errors

TL;DR

Now if you’ve read my articles before, you know I like options. What developer doesn’t like options?! And so, of course, you know what I did. I wrote my own Error Handler. One with options.

If we go back to our first example, the ol’ starter Counter app, and comment out Catcher so to replace it with my little class, ErrorHandler. We’ll get the following when we again click on the ‘+’ button and cause an error:

counter_app_errors.dart

Now it’s not as pretty as Catcher’s error screen, but that’s by design. You see, no ‘high-level’ widgets are used to display the error message. It’s painted on instead of using the RenderBox class. I don’t want to cause any further errors. In development, I just want the error message and the stack trace. That’s it. The only other difference is you’re not seeing red anywhere.

Now that’s just its default behavior. This class has a constructor, of course, that allows you to assign your own ErrorWidget.builder function as well as your own FlutterError.onError exception handler. Make them as complex as you want if you must.

Catcher provides a little more ‘hand-holding’ and supplies pre-defined functionality. With my class, however, it’s all on you. You make up your own ‘error handling’ routines. Further, unlike Catcher, you would have to explicitly incorporate Localization and such into your routines.

Below is a screenshot of the class, ErrorHandler. Note, how the constructor saves the ‘current’ Exception handler and ErrorWidget builder and then sets any routines passed in as parameters. Note, that it’s a conventional class and not a static factory class allowing you to instantiate more than one instance of ErrorHandler. Now, why would you create more than one of this class?

error_handler.dart

Further along in the class, you’ll see there’s the function, dispose (). Also, look at the cute little static getter called, inDebugger. That allows you to determine if you're in development or in production. You could use that in your own defined routines so to display one screen in development and display a different screen in production for example.

error_handler.dart

Crash In The Crash

But what is this all good for? Well, I’ll demonstrate. Looking back at the ‘Startup Name Generator’ example app, let’s incorporate Catcher and invoke the very same error at startup. The screenshots below show Cather’s ‘default Error Widget’ is being used and the result thereof. Notice when the crash occurs, the background is black. That’s because Catcher has had an error itself trying to display its error screen. You see, the app in such a state that additional widgets caused further errors. Well, let’s try something else. We’ll leave everything alone, but now introduce that ErrorHandler class as well.

Your_First_App.dart

Let’s use both Catcher and the ErrorHandler class. Inside the class, RandomWordState, we see it now has its own error handler instantiated when created and it’s cleared when the State object is itself disposed of. Now we’ll start up this app again and cause the error. See what happens now? We’ve got Catcher’s dialog window and ErrorHandler’s error screen. Conceivably, you could assign each State object in your app its own error handler if you want to catch specific errors in a specific fashion depending on the part of the app that’s in error. You can then save some data and close some low-level files, etc. Have error handling specific to that State object. Anyway, it’s an option.

Your_First_App.dart

Again, it’s not pretty, but that simple error screen assures you that there are no further errors as a result of using a customized error handler. Frankly, even using a dialog window may be a little precarious. Of course, Catcher offers an array of options:

Dialog Report Mode

  • shows dialog with information about error. Page Report Mode -shows new page with information about error. Console Handler -is the default and shows crash log in console. Email Manual Handler
  • used to have the user to manually send email. Email Auto Handler -used to automatically send email with error reports. Http Handler -to send the error report to external server. File Handler -to store logs in file. Toast Handler -show a short message in the toast display. Sentry Handler
  • to send handled errors to the application monitoring platform, Sentry.io. Slack Handler -to send messages to your Slack workspace. Discord Handler -to send messages to your Discord workspace.

Anyway, the class, ErrorHandler, uses the very same classes as the default ‘ErrorWidget.builder’ routine to convey its default error message. Again, you’re free to assign your own ‘ErrorWidget.builder’ routine if you want to.

error_handler.dart

Anyway! That class is yours to do what you like. Take it. Make it your own, and maybe share any changes you make — or don’t use it at all. Use Catcher! Use something! Those errors are coming, and you know it. No one wants to see that red screen. Especially, your users.

Cheers.

Flutter Community


Top comments (0)