Tutorials

Build a Flutter root integration

Attach framework and platform hooks inside an application-owned Zone and present errors through the root Navigator.

v0.1.0-dev.1Intermediate

Build a Flutter root integration

This tutorial connects one core manager to the three root failure paths relevant to a typical Flutter application: the application Zone, Flutter framework reporting, and uncaught root-isolate platform errors. Child Isolates remain a separate explicit binding.

text
Zone error ───────────────┐
FlutterError.onError ─────┼─→ one ErrorManager pipeline
PlatformDispatcher error ┘
01

Install the Flutter package

shell
flutter pub add ark_error_manager_flutter:^0.1.0-dev.1

The package exports the core API, so the bootstrap file needs one import.

02

Own bootstrap state explicitly

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

  void run() => runZonedGuarded(_start, _onZoneError);
}

The nullable manager allows the Zone callback to use a direct fallback if manager construction itself fails.

03

Create and attach inside the Zone

dart
void _start() {
  WidgetsFlutterBinding.ensureInitialized();
  final navigatorKey = GlobalKey<NavigatorState>();

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

  final binding = FlutterErrorManagerBinding(manager: manager)..attach();
  _manager = manager;
  _binding = binding;
  runApp(Application(navigatorKey: navigatorKey));
}
04

Provide a construction fallback

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

The binding and manager stay in the bootstrap owner. Widgets, Presenters, UseCases, repositories, and providers do not receive them.

05

Implement application-owned presentation

dart
final class ApplicationErrorPresentationDelegate
    implements FlutterErrorPresentationDelegate {
  const ApplicationErrorPresentationDelegate();

  @override
  Future<void> present(
    BuildContext? context,
    ErrorIncident incident,
    ErrorPresentationDirective directive,
  ) async {
    if (context == null || directive == ErrorPresentationDirective.none) {
      return;
    }
    await showDialog<void>(
      context: context,
      barrierDismissible: directive != ErrorPresentationDirective.blocking,
      builder: (context) => const AlertDialog(
        title: Text('Something went wrong'),
        content: Text('Try the operation again.'),
      ),
    );
  }
}

The delegate receives the raw application-local Incident because it may choose presentation from category, severity, and policy. Do not display exception text, stack traces, or arbitrary context fields to users.

06

Exercise the framework path

dart
FlutterError.reportError(
  FlutterErrorDetails(
    exception: StateError('Example framework failure'),
    stack: StackTrace.current,
    library: 'catalog example',
    context: ErrorDescription('while handling the example action'),
  ),
);

This uses Flutter's normal reporting API. FlutterErrorManagerBinding receives it through FlutterError.onError; the screen itself does not receive the manager.

Ownership during shutdown

Long-running mobile applications often end by process termination, but tests, desktop shells, embedded Flutter hosts, and controlled restarts have a real shutdown sequence:

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

Detach first so a global callback cannot submit a new incident while the manager closes. Store the binding because it owns the captured previous handlers and can restore them safely.

Avoid duplicate reporting

The default previous-handler policy preserves normal Flutter diagnostics. If a previous FlutterError.onError already sends errors to a remote crash service, decide which integration owns external reporting. Preserving two remote reporters without a deduplication plan can upload the same failure twice.

ARKTELOS

Purpose-built engineering systems for software that has to hold.