skip to content

In Flutter, what is the red error screen, and what does a release-build user see when the same widget's build method throws?

level: juniorimportance: should knowfreq 52%

answer

  1. a stand-in for one broken widget
  2. ErrorWidget from ErrorWidget.builder
  3. RenderErrorBox paints it
  4. red only while asserts run
  5. grey, message-less box otherwise

basics

~20 s

The red screen is an ErrorWidget built in place of a widget whose build threw. Debug builds show the exception in yellow on red; profile and release builds, with asserts off, show a plain grey box with no text.

solid answer

~40 s

When a widget's `build` throws, its element catches the exception and reports it through `FlutterError.reportError`. It then asks the static `ErrorWidget.builder` for a replacement. The default builder returns an `ErrorWidget`, whose `RenderErrorBox` paints the message in yellow monospace on a red background. That red colour and the message text are set inside `assert` blocks, so in profile and release builds the box is light grey and empty. Only the failed subtree is replaced; the rest of the screen keeps working. You can assign `ErrorWidget.builder` in `main()` to show a friendlier fallback. It covers failures while building widgets, not overflow stripes and not async errors.

code

dart · 19 lines
dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  if (kReleaseMode) {
    ErrorWidget.builder = (FlutterErrorDetails details) {
      return const ColoredBox(
        color: Color(0xFFEEEEEE),
        child: Center(
          child: Text(
            'This section could not be shown.',
            textDirection: TextDirection.ltr,
          ),
        ),
      );
    };
  }
  runApp(const MaterialApp(home: Scaffold(body: Center(child: Text('ok')))));
}

go deeper

for a junior

Recall that the red screen is ErrorWidget replacing one broken widget, and that users of a release build see a grey box instead.

for a middle

Walk through the order: the element catches, FlutterError.onError runs, then ErrorWidget.builder supplies the replacement; explain why profile matches release.

for a senior

Treat grey boxes as a production signal: make the fallback cheap and on-brand, and make sure the hook behind it forwards to reporting.

for a principal

Decide how a product should degrade when a section fails to build, and who owns the fallback design across teams.

## What the red screen actually is The "red screen of death" is not a crash page. It is an ordinary widget called **`ErrorWidget`**, a `LeafRenderObjectWidget` backed by the render object **`RenderErrorBox`**. Flutter puts it in the tree **in place of the one widget whose build failed**. The parent, its siblings and the rest of the app keep running. If the failure is high in the tree the replacement fills the screen, which is why it looks like a whole-app failure. ## What happens, step by step 1. A widget's `build` throws. This can also be a `LayoutBuilder` builder or a list's item builder, because both build widgets too. 2. The element's rebuild code (`ComponentElement.performRebuild` for ordinary widgets) catches the exception. 3. It creates a `FlutterErrorDetails` with `library: 'widgets library'` and a context such as "building MyCard", and calls `FlutterError.reportError`. Your `FlutterError.onError` runs **first**. 4. It passes the same details to the static **`ErrorWidget.builder`** and mounts whatever widget that returns in the failed widget's place. 5. The next successful rebuild of that part of the tree replaces the error widget again. ## Debug versus profile and release The default builder only fills in the message inside an `assert`. `RenderErrorBox` picks its colours the same way. Asserts run only in debug builds, so profile builds look like release builds: | Build mode | Background | Text | Message | |---|---|---|---| | debug | red | yellow, bold monospace | the exception plus a link to docs.flutter.dev/testing/errors | | profile | light grey | none shown | empty string | | release | light grey | none shown | empty string | The published docs describe the grey box as the release behaviour. The source applies it whenever asserts are off, which includes profile mode. ## Replacing it with ErrorWidget.builder `ErrorWidget.builder` is a static `ErrorWidgetBuilder`, a function from `FlutterErrorDetails` to `Widget`. Assign it before `runApp` (or, as the docs show, inside `MaterialApp.builder`, where the app's theme is available). - **Keep it cheap.** The API doc warns that it runs just after an exception thrown in the middle of build, while surrounding widgets and the `BuildOwner` may be fragile. It recommends a leaf widget that can handle any constraints. - **Keep the red screen in debug.** A common pattern replaces the builder only when `kReleaseMode` is true, so developers still see the message. - **Do not show `details.exception` to users.** It is developer text and may contain data you do not want on screen. - **Recolouring is also possible.** `RenderErrorBox.backgroundColor` and `RenderErrorBox.textStyle` are static fields, but a new builder usually gives a better result. ## What it does not cover - **Overflow stripes.** The yellow-and-black bars on a `Row` or `Column` that overflows are a layout debug paint, not an `ErrorWidget`. - **Layout and paint exceptions.** An exception in `performLayout` or `paint` is reported to `FlutterError.onError` with `library: 'rendering library'`, but no widget is swapped in. - **Async and gesture errors.** A failed `Future` or a throwing `onTap` never goes through the builder. ## Typical messages on the red screen The debug message is the exception's text, and a handful of messages cover most red screens a beginner meets: - **`Null check operator used on a null value`**: a `!` on a value that was `null` at build time, often data that has not loaded yet. - **`RangeError (index)`**: indexing a list with a position it does not have, for example an empty list on the first frame. - **`type 'Null' is not a subtype of type 'String'`**: a failed implicit cast, typically from a decoded JSON map. Each is a bug in that widget's `build` or in the data it reads. The red box points to it, and the console holds the stack trace. ## Why juniors are asked this The question checks two ideas. First, a thrown exception in `build` does not kill a Flutter app; one subtree is replaced. Second, what developers see in debug is not what users see. A red box during development becomes a silent grey hole in production, and the only sign of it is whatever your error hooks send somewhere.

  • Why does a try/catch around a parent's build method not stop a child's red screen?
    A parent's `build` only returns widget configurations; the child's own `build` runs later, when the framework builds that child's element. The exception is thrown and caught there, inside the framework, so the parent's try/catch has already returned. Guard the failing child, or handle the error where its data is produced.
  • Does a layout exception also produce an ErrorWidget?
    No. `ErrorWidget.builder` is called only from code that builds widgets: element rebuilds, `LayoutBuilder`, and sliver child builders. An exception in a render object's `performLayout` or `paint` is reported to `FlutterError.onError` with `library: 'rendering library'`, but no replacement widget is inserted.

An understudy covering one role: in rehearsal they wear a red sash with the reason pinned to it, on opening night a plain grey costume, and the rest of the cast plays on.

saying these in an interview costs you the question

  • The red screen means the whole Flutter app has crashed
  • Release builds show the same red error message to users
  • Only release builds turn grey; profile builds stay red
  • A Row overflow's striped bars come from ErrorWidget
  • ErrorWidget.builder also catches errors in async callbacks