Error Handling in my Flutter App
By: Greg Perry
Tags: Mobile App Development, iOS App Development, Flutter, Programming, Android App Development

Again, looking closely at the runApp() function, we see, if nothing is passed as parameters, the Firebase Crashlytics routines are assigned to handle and report exceptions. The runApp() function displayed above then calls the runApp() functions displayed below. Yet, it too is not the function you’re familiar with. However, this second runApp() function does eventually call the original runApp() function supplied by Flutter, but not before wrapping your app around an error handler. How this error handler works is what we’re going to examine, today.

In The Zone

We’re going to walk-through the code and examine the series of events that occur when an error is triggered in the app. This error will occur when the runApp() function is running and the app is first starting up. In the screenshot above, you can see the error routine, runZonedError, is highlighted. It’s called when an error occurs at startup.

In the screenshot below, you’ll notice I’ve commented-out the Firebase Crashlytics routines for now. Hence, they won't be used in this instance. Let’s instead see what the ‘default behaviour’ is when an error occurs at startup.

Below, is a screenshot of the error routine, runZonedError. When an exception does occur, it merely passes the generated Exception and StackTrace objects to the routine, _debugReportException(). It is this routine that then produces a ‘FlutterErrorDetails’ object and finally passes it to the Flutter framework’s error handler. Of course, since this app uses the MVC framework, the developer is able to define their own error handler.

To Make An Error

To cause the error, I’ve decided to mess with the very framework I use with all my apps, and change some code to intentionally cause an error — I’ll be sure to correct it after we’re done with this exercise.

Listen To The Connection

In this framework, you can assign ‘listeners’ to be triggered if and when the device’s connectivity status changes for one reason or another. For example, when you turn off the wifi or your phone. The class, App, has such a routine to add a listener, and it’s found right in its constructor. As your recall, the App class is extended and called in the main.dart file. A screenshot of this class and its constructor is displayed below.

I made a quick change to the code to cause an error at startup. The function, addConnectivityListener(), is called every time the Flutter app starts up. In most instances, there won’t be a ‘listener’ specified and so there’s an if statement there to prevent any problems. You see, one can’t assign null as a listener, but I’ve commented out that if statement. The app is not going to like that.

Report The Error

And so, in the screenshot below, we’re back where the app is just about to call Flutter’s own error handler. It had started up but quickly encountered that error while running Flutter’s runApp() function. In the screenshot below, we see the FlutterErrorDetails object was created and, again, is just about to be passed to Flutter’s reportError() function. As you know from your own experiences with Flutter, this usually results in the ‘Red Screen of Death’ or at the very least, the error message and stack trace displayed in your IDE’s console screen.

Let’s that a quick peek inside Flutter’s reportError() function. You can see that it, in turn, calls the routine assigned to the static variable, onError. Note, the reportError() function is called almost every time an exception occurs in your Flutter app.

Dump The Error

By design, the default behavior for the static function, FlutterError.onError, is to call yet another static function, dumpErrorToConsole. A very descriptive name — you’re familiar with this as it lists the error right out on the IDE’s console screen. In fact, below is a screenshot of this original routine.

However, in this framework, it’s the function below that’s called instead Below is a screenshot with a breakpoint stopping the running code. In this MVC framework, error handling is to be as versatile as possible. For example, the onError() function below could have been overridden by the developer to customize the error handling. However, you can see the ‘App Controller’, con, has its own error handler — a developer could have customized that error handler instead. Options.

Let’s now look at that AppController’s error handler. We can see you if you didn’t override it with your own error routine, it turns to it’s associated State object and it’s error handler. At last count, that’s three places in the framework for error handling to be adapted to specific requirements of a Flutter app. Three places the developer or developers could have overridden. Good to have options.

By design, in this framework, each State object can have its own error handler. Each State object can have any number of Controllers associated with it, and it’s standard for each Controller to then rely on the State object’s error handler — and that’s what you’re seeing in this code. At this point, the State object can either have its own error handler defined or will simply use the ‘old’ or ‘default’ error handler. As it happens, in this case, it’s the old static function, dumpErrorToConsole, that’s the default error handler. Let’s see how this all works.

And so, when the instance variable, handler, is executed in the screenshot above, the old static function, dumpErrorToConsole, is indeed called and the error listed in the IDE’s console window as you see below.