Better Errors Messages for Flutter

Better Errors Messages for Flutter

Better Errors Messages for Flutter

Imagine that it’s blinking…

… is probably an article I should have published on April 1st. Still, here it is. The Amiga had an iconic way to display fatal errors: The guru meditation alert. Let’s recreate this for Flutter.

Here is how I want to display the alert:

showAlert(context, 42);

Instead of complicated and talkative text messages, I will display succient hexadecimal error codes which need no translation or localization. I’m utterly convinced that this is the future.

import 'package:flutter/widgets.dart';
void showAlert(BuildContext context, int code) {
  final hex = code.toRadixString(16).toUpperCase().padLeft(8, '0');
  ...
}

Notice, how I not try to include a library for left-padding my string for improved stability and protection against rage-quitting developers.

To display anything above the normal UI, an OverlayEntry which is then added to the current Overlay can be used. The such an Overlay is automatically created as part of a WidgetsApp or MaterialApp widget. The overlay entry must be explicitly removed again. I’m using a GestureDetector so that the user has to tap the overlay to make it go away.

void showAlert(BuildContext context, int code) {
  final hex = code.toRadixString(16).toUpperCase().padLeft(8, '0');
  final black = Color(0xFF000000);
  final red = Color(0xFFFF0000);
  OverlayEntry overlay;
  overlay = OverlayEntry(builder: (context) {
    return Positioned(
      left: 0,
      right: 0,
      child: GestureDetector(
        onTap: () => overlay.remove(),
        child: Container(
          color: black,
          height: 128,
        ),
      ),
    );
  });
  Overlay.of(context).insert(overlay);
}

Because I don’t want to depend on material.dart I create my own Color objects. And because I need to refer to the overlay variable inside of the onTap handler of the builder function I cannot use the usual final variable definition that declares and initializes a variable in one step. Overlays are a bit special.

The Overlay widget is basically a Stack so I can use a Positioned widget to position my alert (currently just a black Container wrapped inside a GestureDetector) at the top edge of the screen.

At this point, I can display a black overlay which is removed if I tap it.


Because an OverlayEntry isn’t automatically wrapped with a DefaultTextStyle, Text widgets without style or with a TextStyle that does not explicitly prohibits inheritance are displayed with a very ugly default text style (large redish text that has yellow double-underlining). Therefore, it is important to add an inherit: false property like so:

          Container(
            alignment: Alignment.center,
            color: black,
            height: 128,
            child: Text(
              'Software Failure.  Tap to continue.\n\n'
              'Guru Meditation #48454C50.$hex',
              textAlign: TextAlign.center,
              style: TextStyle(
                color: red,
                fontFamily: 'Courier',
                fontSize: 16,
                fontWeight: FontWeight.bold,
                inherit: false,
              ),
            ),
          )

It starts to look like the real deal (please ignore the notch for now):


Let’s add the iconic red border next. There’s one problem, though. I don’t want the text to wrap (which would happen on smaller devices). Furthermore, if you compare the Courier font I used with the original Amiga font, it runs too wide. So let’s transform the Text widget by scaling it horizontally to 75%. To keep it at the same width, I have to enlarge the width by 133% at the same time. The text should now always fit the screen.

          Container(
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: black,
              border: Border.all(color: red, width: 8),
            ),
            padding: EdgeInsets.symmetric(
              horizontal: 8,
              vertical: 16,
            ),
            child: FractionallySizedBox(
              widthFactor: 4 / 3,
              child: Transform(
                alignment: Alignment.center,
                transform: Matrix4.identity().scaled(3 / 4, 1),
                child: Text(
                  ...
                ),
              ),
            ),
          ),

We’re almost there…


As you probably see on my screen shots, if the device has a notch, the alert doesn’t look right. I’d like to move the box below the notch but cover everything above in black. The Amiga also displays a small black border around the red border, so let’s add this, too. A SafeArea widget inside another Container can do both. I need to disable its bottom margin, though.

    return Positioned(
      left: 0,
      right: 0,
      child: Container(
        color: black,
        child: SafeArea(
          minimum: EdgeInsets.all(8),
          bottom: false,
          child: GestureDetector(
            ...
          ),
        ),
      ),
    );

And there, it is, perfectly aligned:


For the final and most important step, I will add blinking.

The following might be a bit hacky, I don’t know, but it works and I don’t have to create a custom StatefulWidget. I’m using a StatefulBuilder instead. Each time, that widget asks its builder function to recreate the UI, I setup a timer to toggle the border color from red to black and back again after 700ms. Then, I’m using an AnimatedContainer to implicitly animate this color change within 300ms. Some things are just too easy in Flutter.

          GestureDetector(
            onTap: () {
              blink = null;
              overlay.remove();
            },
            child: StatefulBuilder(
              builder: (context, setState) {
                Future.delayed(Duration(milliseconds: 700))
                .then((_) {
                  if (blink != null) setState(() => blink = !blink);
                });
                return AnimatedContainer(
                  duration: Duration(milliseconds: 300),
                  curve: Curves.easeInOut,
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    color: black,
                    border: Border.all(
                      color: blink ? red : black, width: 8),
                  ),
                  ...
                );
              },
            ),
          ),

Here is the hacky part: Because Flutter throws an exception if I tap the alert and setState is then called on a disposed widget, I need to protect myself against this case by setting blink to null and explicitly checking for it. Perhaps, I should have used a Timer instead of a Future because that timer could be cancelled, I think. The future is inevitable.


Back to the future…

And there you have it, an alert box that looks much better than the usual modal dialog. And at least with old folk like myself, people will have a positive nostalgic feeling instead of cold-blooded anger because your app didn’t work for them as expected.

Here is the source code, by the way.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on Facebook | Twitter

Learn More

Learn Flutter & Dart to Build iOS & Android Apps

Flutter & Dart - The Complete Flutter App Development Course

Dart and Flutter: The Complete Developer’s Guide

Flutter - Advanced Course

Flutter Tutorial - Flight List UI Example In Flutter

A Design Pattern for Flutter

An introduction to Dart and Flutter

Introduction to Flutter: Building iOS and Android Apps from a Single Codebase

Building DashCast, a podcast app in Flutter

flutter mobile-apps ios android

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How long does it take to develop/build an app?

This article covers A-Z about the mobile and web app development process and answers your question on how long does it take to develop/build an app.

Top 25 Flutter Mobile App Templates in 2020

Flutter has been booming worldwide from the past few years. While there are many popular mobile app development technologies out there, Flutter has managed to leave its mark in the mobile application development world. In this article, we’ve curated the best Flutter app templates available on the market as of July 2020.

Best Mobile App Development Company | Android and iOS Apps

iPrism Tech is a one of the best and offshore mobile app development company in India, Saudi Arabia and USA. We are a major providers of android, iphone and ipad mobile app development services at economical prices.

How To Succeed In Mobile App Wireframe Design?

This article covers everything about mobile app wireframe design: what to do and what not, tools used in designing a mobile or web app wireframe, and more.

Google's Flutter 1.20 stable announced with new features - Navoki

Google has announced new flutter 1.20 stable with many improvements, and features, enabling flutter for Desktop and Web