Ark Error Manager Flutter

Flutter runtime hooks, build-mode detection, context collection, and presentation integration for ark_error_manager.

The package connects one application-owned ErrorManager to FlutterError.onError and PlatformDispatcher.onError. It does not place the manager in widgets, Presenters, UseCases, repositories, providers, or DI scopes.

Русская версия

Version 1.0 establishes the stable public API described in this guide.

What the adapter captures

Flutter has two root error paths:

  • FlutterError.onError receives failures caught by Flutter framework callbacks, including build, layout, paint, and explicit FlutterError.reportError calls;
  • PlatformDispatcher.onError receives unhandled root-isolate errors outside Flutter callbacks when no nearer error Zone handles them.

FlutterErrorManagerBinding attaches both paths to the same manager. A root Zone remains application-owned and uses the core ErrorManager.onUncaughtZoneError callback.

Installation

dependencies:
  ark_error_manager_flutter: ^1.0.0
import 'package:ark_error_manager_flutter/ark_error_manager_flutter.dart';

The Flutter package exports the public core API, so a separate core import is not required in the same file.

Bootstrap composition

Initialize Flutter, create the manager, attach Flutter hooks, and call runApp inside the same application-owned Zone:

The excerpt focuses on ownership. Application and ApplicationErrorPresentationDelegate are application-defined types; the runnable example contains a complete implementation.

import 'dart:async';

import 'package:ark_error_manager_flutter/ark_error_manager_flutter.dart';
import 'package:flutter/material.dart';

void main() => ApplicationBootstrap().run();

final class ApplicationBootstrap {
  ErrorManager? _manager;
  FlutterErrorManagerBinding? _binding;

  void run() {
    runZonedGuarded(
      _start,
      _onZoneError,
    );
  }

  void _start() {
    WidgetsFlutterBinding.ensureInitialized();

    final GlobalKey<NavigatorState> navigatorKey =
        GlobalKey<NavigatorState>();
    final ErrorManager manager = ErrorManager(
      configuration: ErrorManagerConfiguration(
        environment: FlutterErrorBuildMode.environment(
          deployment: ErrorDeploymentEnvironment.production,
          application: const ErrorApplicationInfo(
            name: 'Example application',
            version: '1.4.0',
          ),
        ),
        classifiers: const <ErrorClassifier>[
          FlutterFrameworkErrorClassifier(),
        ],
        contextProviders: <ErrorContextProvider>[
          FlutterRuntimeErrorContextProvider(),
        ],
        reporters: const <ErrorReporter>[
          DeveloperLogErrorReporter(),
        ],
        presenter: NavigatorErrorPresenter(
          navigatorKey: navigatorKey,
          delegate: const ApplicationErrorPresentationDelegate(),
        ),
      ),
    );
    final FlutterErrorManagerBinding binding =
        FlutterErrorManagerBinding(manager: manager)..attach();

    _manager = manager;
    _binding = binding;
    runApp(Application(navigatorKey: navigatorKey));
  }

  void _onZoneError(Object error, StackTrace stackTrace) {
    final ErrorManager? manager = _manager;
    if (manager == null) {
      Zone.root.handleUncaughtError(error, stackTrace);
      return;
    }
    manager.onUncaughtZoneError(error, stackTrace);
  }
}

The bootstrap object retains the binding only so a controlled application host can later call detach, then flush and close on the manager. Widgets and features do not receive either object.

Hook ownership

Flutter's handlers are global inside the root isolate. The binding therefore has explicit ownership rules:

  • repeated attach on the same binding is idempotent;
  • attaching a different binding while one is active throws StateError;
  • detach restores handlers that existed before attach;
  • a handler installed by a later external owner is never overwritten during detach;
  • isAttached exposes current ownership state.

This prevents silent hook replacement and stale-manager restoration.

Previous handlers

FlutterPreviousErrorHandlerPolicy.preserve is the default. The binding sends the error to the manager and then invokes the previous handler. This preserves Flutter's normal debug output and allows intentional composition with an existing host handler.

Use replace when the manager must be the sole owner and the application has already moved the previous handler's behavior into reporters or policy.

Do not install two crash-reporting systems independently and preserve both without checking for duplicate remote reports.

Flutter error details

Framework errors include FlutterErrorCaptureDetails, which retains the original FlutterErrorDetails for application-local classification, presentation, and recovery. Raw details never appear in ErrorReport.

FlutterRuntimeErrorContextProvider adds bounded diagnostics:

  • target platform and web flag;
  • application lifecycle state;
  • locale list;
  • platform brightness;
  • Flutter library and operation context;
  • Flutter's silent diagnostics flag.

The provider does not collect widget trees, user-entered values, route arguments, or arbitrary framework diagnostics.

Build mode and deployment environment

FlutterErrorBuildMode.detect() maps Flutter's compile-time constants to core ErrorBuildMode.debug, profile, or release.

FlutterErrorBuildMode.environment(...) combines that value with an explicit deployment target and application identity:

final ErrorRuntimeEnvironment environment =
    FlutterErrorBuildMode.environment(
      deployment: ErrorDeploymentEnvironment.staging,
      application: const ErrorApplicationInfo(
        name: 'Example application',
        version: '1.4.0',
        buildNumber: '87',
        packageIdentifier: 'dev.example.application',
      ),
    );

The adapter does not infer whether a build talks to development, staging, or production services.

Presentation

NavigatorErrorPresenter adapts the core ErrorPresenter contract to Flutter. It looks up the current context from an application-owned GlobalKey<NavigatorState> and delegates rendering to FlutterErrorPresentationDelegate.

The package does not impose Material dialogs, SnackBars, navigation, or copy. The delegate receives:

  • current root navigation context, which can be null during startup or shutdown;
  • raw application-local ErrorIncident;
  • passive or blocking presentation directive.

The delegate must handle a null context without throwing.

Relationship to other Ark packages

No Ark package depends on Ark Error Manager. Existing propagation remains the integration:

  • ark_mvp_flutter reports unhandled runtime failures through FlutterError.reportError;
  • ark_di_flutter forwards unhandled scope-close failures to the owning Zone;
  • usecase_forge_flutter reports widget-side failures through Flutter;
  • Ark Data Layer does not swallow unexpected operation failures.

One root binding receives those errors without manager injection into feature objects.

Controlled shutdown

For hosts that support controlled shutdown:

binding.detach();
await manager.flush();
await manager.close();

Detach first so no new Flutter events enter a manager that is closing.

Further reading

License

Apache License 2.0. See LICENSE and NOTICE.

Libraries

ark_error_manager_flutter
Flutter capture hooks and presentation integration for Ark Error Manager.