flutter_mayday 0.2.0 copy "flutter_mayday: ^0.2.0" to clipboard
flutter_mayday: ^0.2.0 copied to clipboard

On-device crash reporting for Flutter. Captures device, session, network, logs, errors, and performance into a single shareable .blackbox file — no server, no account, no dashboard required.

flutter_mayday #

pub.dev License: MIT Flutter Platform

On-device crash reporting for Flutter — no server, no account, no dashboard.

When something goes wrong in production, BlackBox gives your developer everything they need to reproduce the bug in a single file they can attach to any message.


The problem #

Mobile bugs are notoriously hard to reproduce. By the time a developer receives a vague bug report, the device state, network responses, navigation history, and logs that caused the crash are gone.

The solution #

BlackBox runs inside your app like an airplane's flight data recorder. It continuously writes context into fixed-size in-memory buffers — zero disk I/O during normal operation. When a crash fires or a tester taps the badge, it assembles one self-contained .blackbox file containing everything needed to reproduce the bug.

One file. No upload. No account. Attach it to a GitHub issue, Slack message, or support ticket and your developer can open the in-app viewer or decode it locally in seconds.


Features #

  • 📦 One-file reports — everything in a gzip-compressed, CRC32-verified .blackbox envelope
  • 🔍 In-app viewerBlackBoxViewerScreen renders the full report on-device, structured and searchable
  • 🛡️ Privacy-first — PII is redacted before any file is written: key blocklist, JWT, credit card, and custom regex rules
  • Zero runtime overhead — bounded circular buffers, no disk I/O, no network calls during normal operation
  • 📳 Shake to report — shake the device to open the viewer or share a file instantly
  • 📸 Screenshot on crash — captures the screen automatically when a fatal error fires
  • 🌐 Dio integration — one interceptor captures every request, response, and error
  • 🏗️ State adapters — snapshot BLoC, GetX, or any custom state manager at report time
  • 📊 Four export formats.blackbox, .json, .md, .html
  • 🔇 Off modeBlackBoxMode.off makes every call a no-op for enterprise opt-out
  • 🎣 onBeforeExport hook — transform, enrich, or strip the report before it is written

What's in every report #

Section Captured data
App Name, version, build number, flavor, Flutter & Dart version, compilation mode, git commit
Session Unique session ID, start time, duration, crash-free session count before this one
Device Manufacturer, model, OS version, screen dimensions, DPR, locale, timezone, accessibility flags
Environment Battery level & charging state, network type, memory pressure, text scale, brightness
Crash Error type, message, full stack trace, source (Flutter / platform dispatcher / isolate)
Errors All fatal and non-fatal errors recorded this session
Navigation Current route, route stack, full history with push/pop/replace actions and timestamps
Network Every Dio request — URL, method, status, latency, request & response headers and truncated bodies
Logs All log entries with level, tag, extras, and timestamp
Performance Total frames, janky frames, janky%, average and worst frame ms, estimated FPS
Breadcrumbs Custom event trail with labels, extras, and timestamps
State Snapshot of every registered state adapter (BLoC, GetX, custom)

Quick start #

1. Add the dependency #

dependencies:
  flutter_mayday: ^0.2.0

2. Initialise before runApp #

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

// Share this key between BlackBox and MaterialApp so the overlay can navigate.
final _navKey = GlobalKey<NavigatorState>();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await BlackBox.init(
    config: const BlackBoxConfig(
      mode: BlackBoxMode.always,         // verbose in debug, always in release
      onCrash: BlackBoxOnCrash.exportAndShare,
      shakeToReport: true,
      screenshotOnCrash: true,
    ),
  );

  BlackBox.setNavigatorKey(_navKey);     // required for shake / badge tap

  runApp(
    BlackBoxScope(                       // wraps your whole app
      child: MaterialApp(
        navigatorKey: _navKey,
        navigatorObservers: [BlackBox.navigationObserver],
        home: const MyApp(),
      ),
    ),
  );
}

Why the navigator key?
BlackBoxScope sits above MaterialApp to catch errors before the widget tree tears down. This means the overlay's context has no Navigator ancestor. Passing the same GlobalKey<NavigatorState> to both BlackBox.setNavigatorKey() and MaterialApp(navigatorKey:) gives the overlay a direct reference to the navigator state.

3. Add the Dio interceptor #

final dio = Dio();
dio.interceptors.add(BlackBox.dioInterceptor);

That's it. Crashes, logs, navigation, and network traffic are now captured automatically.


In-app viewer #

BlackBoxViewerScreen renders the full report snapshot inside your app — no external tools needed.

Open it programmatically:

Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => const BlackBoxViewerScreen()),
);

Or use the badge controls:

Gesture Action
Tap the badge Opens BlackBoxViewerScreen
Long-press the badge Shares the .blackbox file via the system sheet
Shake the device Opens the viewer (when shakeToReport: true)

The badge appears in the bottom-right corner whenever shakeToReport: true. In production you can disable it and trigger the viewer from your own support screen.


State adapters #

Register adapters once — state is captured lazily at export time, never on every change.

BLoC / Cubit #

BlackBox.registerAdapter(
  BlocStateAdapter<CartState>(
    bloc: cartBloc,
    name: 'cart',
    serialiser: (state) => state.toJson(),
  ),
);

GetX #

BlackBox.registerAdapter(
  GetXStateAdapter<ProfileController>(
    controller: profileController,
    name: 'profile',
    serialiser: (c) => c.toJson(),
  ),
);

Custom (any state manager) #

class AppSettingsAdapter extends StateAdapter {
  @override
  String get name => 'settings';

  @override
  Map<String, dynamic> captureState() => {
    'theme': AppSettings.theme,
    'locale': AppSettings.locale,
    'flags': AppSettings.featureFlags,
  };
}

BlackBox.registerAdapter(AppSettingsAdapter());

Manual API #

// Breadcrumbs — build a user journey trail
BlackBox.addBreadcrumb('user_tapped_checkout', extras: {'cart_id': 'abc'});

// Structured logging — five levels
BlackBox.log(BlackBoxLogLevel.warning, 'Retry attempt 3', tag: 'network');
BlackBox.log(BlackBoxLogLevel.info, 'Session started', extras: {'plan': 'pro'});

// User identity — PII-scrubbed on export
BlackBox.identify(userId: 'u_123', traits: {'plan': 'pro', 'role': 'admin'});

// Non-fatal capture — records error, adds breadcrumb, writes report
await BlackBox.capture(
  label: 'payment_failed',
  error: exception,
  stackTrace: stackTrace,
  extras: {'amount': 99.0, 'currency': 'USD'},
);

Export #

// Write to a file
final file = await BlackBox.export(format: ReportFormat.blackbox);
final json  = await BlackBox.export(format: ReportFormat.json);

// Write + share via system sheet
await BlackBox.share(format: ReportFormat.blackbox);

// Build report map for in-app display (no file written)
final report = await BlackBox.buildSnapshot();

Formats #

Format Extension Best for
blackbox .blackbox Sharing — compact gzip binary, decoded with BlackboxDecoder
json .json Tooling — pretty-printed, opens in any editor
markdown .md Documentation — renders on GitHub and GitLab
html .html Quick review — opens directly in any browser

Privacy & PII scrubbing #

BlackBox never uploads data. Reports are generated on-device and only written when your code asks for it.

Every export is recursively scrubbed before being written:

Rule What it catches
Key blocklist password, token, authorization, secret, api_key, access_token, refresh_token, private_key, credential — and any keys you add
JWT bearer tokens Any Authorization: Bearer eyJ… value
Credit card numbers Any sequence that passes the Luhn checksum
Custom keys Via BlackBoxConfig.redactKeys
Custom patterns Via BlackBoxConfig.redactPatterns
BlackBoxConfig(
  redactKeys: const ['ssn', 'date_of_birth', 'routing_number'],
  redactPatterns: [
    RegExp(r'\b\d{3}-\d{2}-\d{4}\b'),   // SSN
    RegExp(r'\b\d{9}\b'),                // routing number
  ],
)

Configuration reference #

BlackBoxConfig(
  // Collection mode
  mode: BlackBoxMode.always,              // always | verbose | off

  // Crash behaviour
  onCrash: BlackBoxOnCrash.exportAndShare, // none | exportOnly | exportAndShare
  screenshotOnCrash: true,

  // Overlay / shake
  shakeToReport: true,

  // Buffer sizes
  maxLogEntries: 500,
  maxNetworkEntries: 100,
  maxNetworkBodyBytes: 4096,             // bytes per request/response body
  maxBreadcrumbs: 100,
  frameWindowSeconds: 10,               // rolling window for frame timing

  // Privacy
  redactKeys: const ['ssn'],
  redactPatterns: const [],

  // Export
  defaultExportFormat: ReportFormat.blackbox,

  // Hook — transform the report map before writing
  onBeforeExport: (report) async {
    return {...report, 'server_region': await fetchRegion()};
  },
)

Mode comparison #

Mode Use case Overhead Extra collectors
always Production Minimal — bounded in-memory buffers All core collectors
verbose Debug & staging Higher — tracks user input + gesture capture, widget tree dump
off Enterprise opt-out Zero None — every call is a silent no-op

onBeforeExport hook #

Transform the full report map before it is sanitised and written. Async is supported.

onBeforeExport: (report) async {
  // Add server-side enrichment
  final flags = await FeatureFlags.fetchAll();
  return {
    ...report,
    'feature_flags': flags,
    'server_region': Platform.environment['REGION'] ?? 'unknown',
  };
},

.blackbox file format #

┌─────────────────────────────────────────────┐
│  Magic      │  42 4C 4B 42  (BLKB)          │
│  Version    │  00 01                         │
│  Flags      │  bit 0 = gzip, bit 1 = screenshot │
│  Length     │  uint32 big-endian payload len │
│  Payload    │  gzip-compressed UTF-8 JSON    │
│  CRC32      │  uint32 checksum of JSON bytes │
└─────────────────────────────────────────────┘

Decode programmatically:

import 'package:flutter_mayday/flutter_mayday.dart';

final bytes = await file.readAsBytes();
final report = BlackboxDecoder.decode(bytes); // Map<String, dynamic>

Complete setup example #

import 'package:flutter_mayday/flutter_mayday.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

final _navKey = GlobalKey<NavigatorState>();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await BlackBox.init(
    config: BlackBoxConfig(
      mode: kDebugMode ? BlackBoxMode.verbose : BlackBoxMode.always,
      onCrash: BlackBoxOnCrash.exportAndShare,
      shakeToReport: true,
      screenshotOnCrash: true,
      redactKeys: const ['ssn', 'dob'],
      onBeforeExport: (r) async => {...r, 'app_env': 'production'},
    ),
  );

  BlackBox.setNavigatorKey(_navKey);
  BlackBox.identify(userId: 'u_001', traits: {'plan': 'pro'});

  // Dio
  final dio = Dio()..interceptors.add(BlackBox.dioInterceptor);

  // BLoC
  BlackBox.registerAdapter(BlocStateAdapter<AuthState>(
    bloc: authBloc,
    name: 'auth',
    serialiser: (s) => s.toJson(),
  ));

  runApp(BlackBoxScope(
    child: MaterialApp(
      navigatorKey: _navKey,
      navigatorObservers: [BlackBox.navigationObserver],
      home: const MyHomePage(),
    ),
  ));
}

Platform support #

Platform Supported
Android
iOS
macOS
Web
Windows
Linux

iOS notes #

iOS sandboxing limits some diagnostics. ANR detection always returns false — iOS does not expose Android-style process error APIs. Memory pressure is reported via DispatchSource.makeMemoryPressureSource. Some memory fields may be null depending on OS availability.


Contributing #

Run flutter analyze and flutter test before opening a pull request.

Package goals: on-device first · privacy first · bounded memory · clean public API · no generated code · no server dependency.


Built with ❤️ by Yudiz Solutions

1
likes
110
points
80
downloads

Documentation

API reference

Publisher

verified publisheryudiz.com

Weekly Downloads

On-device crash reporting for Flutter. Captures device, session, network, logs, errors, and performance into a single shareable .blackbox file — no server, no account, no dashboard required.

Repository (GitHub)
View/report issues

Topics

#crash-reporting #debugging #logging #monitoring #error-handling

License

MIT (license)

Dependencies

archive, battery_plus, connectivity_plus, device_info_plus, dio, flutter, flutter_bloc, get, package_info_plus, path_provider, sensors_plus, share_plus, shared_preferences, synchronized

More

Packages that depend on flutter_mayday

Packages that implement flutter_mayday