When a Flutter app replaces FlutterError.onError, why should the handler still call FlutterError.presentError, and what does FlutterErrorDetails carry?
answer
- default chain: onError to console
- presentError defaults to dumpErrorToConsole
- IDE structured errors swap presentError
- exception, stack, library, context
- silent skipped outside debug
basics
~20 sFlutterError.onError defaults to presentError, which prints the error and is the hook the IDE's structured error view replaces. A custom handler that skips presentError loses that output. FlutterErrorDetails carries the exception, stack, library, context, an information collector and a silent flag.
solid answer
~40 sThe chain is `FlutterError.reportError` calling `FlutterError.onError`, whose default is `FlutterError.presentError`, which defaults to `dumpErrorToConsole`. When DevTools or an IDE enables structured errors, it replaces `presentError`, not `onError`. A custom `onError` that forwards to a reporter should therefore call `FlutterError.presentError(details)` first, or you lose both the console dump and the IDE view. The details object holds `exception` (required), an optional `stack`, `library` (defaults to `'Flutter framework'`), a `context` node such as "building ProductCard", a lazy `informationCollector`, a `stackFilter`, and `silent`. `silent` is `false` by default. The console dumper ignores it in debug, but otherwise skips silent errors unless forced. Setting `onError` to `null` swallows framework errors silently.
go deeper
Know that FlutterError.onError is the framework's error hook and that its default prints the error to the console.
Explain the chain from reportError to onError to presentError to dumpErrorToConsole, and why a custom handler calls presentError first.
Use the details well: forward library and context with the exception, decide how to treat silent errors, and keep the handler from throwing.
Set a team convention for one owner of the global hooks, and for what an error report must contain so it can be acted on.
## The default chain Every error the framework catches travels the same short chain: 1. A framework call site catches an exception and builds a **`FlutterErrorDetails`**. 2. It calls **`FlutterError.reportError(details)`**, which calls `FlutterError.onError` if it is not `null`. 3. **`FlutterError.onError`** defaults to `FlutterError.presentError`. 4. **`FlutterError.presentError`** defaults to `FlutterError.dumpErrorToConsole`. Each link is a static field you can reassign. They exist for different reasons. `onError` is where an **app** decides what an error means, such as forwarding it, counting it or exiting. `presentError` is how an error is **shown to the developer**. The source notes that tools replace `presentError`: when the structured errors service extension is enabled, the IDE or DevTools swaps in its own presenter so it can render the error as an inspectable tree. ## Why a custom handler calls presentError If you write `FlutterError.onError = (d) => reporter.send(d);`, the default presenter never runs: - the console no longer shows the error during development; - the IDE's structured error view goes quiet, because it lives behind `presentError`; - `flutter logs` and device logs lose the text you would read when debugging a device. Calling `FlutterError.presentError(details)` at the start of your handler keeps all of that and adds your forwarding on top. Both the docs and the API comment recommend it. Two more rules from the source apply: - **Do not call `FlutterError.onError` directly.** Call `FlutterError.reportError`, which checks for `null` and forwards. Your own `catch` blocks can use it to push a caught error through the same pipeline. - **An exception thrown by your handler is not caught** by the framework, so keep the handler defensive. ## What FlutterErrorDetails carries | Field | Type and default | What it tells you | |---|---|---| | `exception` | `Object`, required | the thrown object, not always an `Exception` | | `stack` | `StackTrace?` | where it was thrown, if known | | `library` | `String?`, default `'Flutter framework'` | which layer caught it: `widgets library`, `rendering library`, `gesture` | | `context` | `DiagnosticsNode?` | what was happening, such as "building ProductCard" | | `informationCollector` | `InformationCollector?` | lazily adds diagnostics such as the failing element's debug creator | | `stackFilter` | `IterableFilter<String>?` | custom stack line filtering for display | | `silent` | `bool`, default `false` | marks errors the dumper may skip outside debug | It also offers **`exceptionAsString()`** for a message and a **`summary`** node for one-line logs. A few framework errors are marked `silent: true`, such as image loading failures, which "could be a network error or whatnot". ## How dumpErrorToConsole behaves - **Debug mode ignores `silent`** and prints everything. - **Outside debug**, silent errors are skipped unless `forceReport: true` is passed. - **The first error prints in full.** In debug that is a structured diagnostics tree; in profile and release it is the exception label and a stack of up to 100 frames. - **Later errors print one line**: `Another exception was thrown: <summary>`. They stay short until `FlutterError.resetErrorCount()` is called. That last point often confuses developers who scroll the console and find only one-liners. The full diagnostics belong to the **first** error, which is usually the root cause. ## A worked handler, in order 1. Call `FlutterError.presentError(details)` so the console and IDE keep working. 2. Return early for anything you decided not to report, such as `details.silent` errors in release. 3. Build the report from `details.exceptionAsString()`, `details.stack`, `details.library` and `details.context`. 4. Hand it to the reporter inside a `try`/`catch`, because anything your handler throws escapes the framework. ## Choices for a custom handler - **Forward selectively.** `reportError` calls `onError` whether or not the details are `silent`, so a handler that wants to match console behaviour checks `details.silent` itself. - **Add context.** Send `details.library` and a string form of `details.context` with the exception so a report says where it happened, not just what. - **Never assign `null`.** The source documents that a `null` `onError` silently catches and ignores framework errors, which hides every build failure.
- Why do later framework errors in the console show only one line?`dumpErrorToConsole` keeps an error count. The first error prints full diagnostics; each later one prints `Another exception was thrown:` followed by its summary, until `FlutterError.resetErrorCount()` resets the count or a caller passes `forceReport: true`. The first full dump is usually the root cause, and the one-liners are often its knock-on effects.
- Does silent: true stop an error from reaching a custom FlutterError.onError?No. `FlutterError.reportError` calls `onError` for every details object. `silent` only matters to `dumpErrorToConsole`, which skips silent errors outside debug. A custom handler that wants the same filtering has to check `details.silent` itself.
saying these in an interview costs you the question
- A custom FlutterError.onError keeps the console output automatically
- FlutterErrorDetails.silent stops the error reaching a custom onError
- Setting FlutterError.onError to null makes framework errors crash the app
- Every repeated framework error prints its full diagnostics to the console
- Call FlutterError.onError directly from your own catch blocks