DEV Community

Jaimin Raval
Jaimin Raval

Posted on

I Built an All-in-One Debug Overlay for Flutter That Replaces 6 Separate Tools

No more switching between Proxyman, print statements, and prayer — everything your QA and dev team needs, directly inside the app.


Every Flutter developer has lived this cycle.

You ship a build to QA. They come back: "The user list isn't loading." You ask for a screenshot. They send a screenshot of the empty screen. You ask what API call failed. They don't know. You ask what device it was on. They send you an Android model number from 2019 that you've never heard of.

So you add a bunch of print() statements, rebuild, and hand them another build. Repeat this three times, burn half a day, and finally discover the issue was a 401 because a token wasn't being passed in the header.

There had to be a better way.


What I Built

flutter_blackbox is an all-in-one in-app debug and QA overlay for Flutter. Shake the device (or tap a floating button, or press F12 on desktop) — a full debug panel slides up, directly inside your running app, with no external proxy or tooling required.

Here's what's inside:

Panel What it does
🌐 Network Every HTTP request, live. Headers, body, status, timing bars, cURL export, HAR export. Swipe to dismiss.
🎛 Mocking Intercept any API call and replace it with a local JSON response. Throttle slider to simulate slow networks.
📋 Logs Auto-captures every debugPrint() and print(). Filterable by level, searchable by tag.
Performance Live FPS graph, jank detection, frame budget visualization.
🔄 Rebuilds Tracks widget rebuild counts in real time. Heat-colored ranking. Auto mode needs zero code changes.
💾 Storage Inspect, edit, delete key-value pairs from SharedPreferences, GetStorage, Hive, or any backend. Sensitive keys auto-redacted.
🔌 Socket IO Auto-captures all incoming Socket.IO events. Zero changes to your socket code.
📱 Device Platform, OS version, screen metrics, pixel ratio, connectivity status — in glassmorphic cards.
🐛 Crashes Auto-catches Flutter framework errors, async exceptions, and unhandled platform errors.
🗺️ Journey Records every route change and tap so you can reconstruct exactly what a user did before a crash.
📝 QA Reports One-tap Markdown report: screenshot + device info + journey + failed requests + stack traces. Paste directly into Jira or GitHub Issues.
📊 Mini HUD Drag the floating button to the bottom edge for a persistent live bar showing FPS, HTTP status counts, and crash count.

All of this compiles out of release builds via kDebugMode. Zero runtime cost in production. Zero optional dependencies — the package is 63 KB.


Zero Runtime Cost, Zero Bloat

This was the hardest constraint to get right.

Most debug tools add their dependencies to your production app. If your tool depends on dio, every user of your app is carrying that dependency whether they use the debug panel or not. That's not acceptable.

BlackBox solves this with a CLI-generated adapter architecture — the same pattern used by build_runner, freezed, and injectable. The package itself ships with zero optional dependencies. When you add it to your project, you run:

dart run flutter_blackbox:init --generate
Enter fullscreen mode Exit fullscreen mode

The CLI reads your pubspec.yaml, detects which libraries you actually use (Dio, http, Socket.IO, SharedPreferences), and generates a lib/blackbox_adapters.dart file containing only the adapters your project needs. Dio users don't get http installed. Socket.IO users don't get SharedPreferences adapters. You get exactly what you use.

🐞 BlackBox Init — Auto-detecting your dependencies...

✅ Found dio          → Dio HTTP client
⬚  http              → not found, skipping
⬚  socket_io_client  → not found, skipping
✅ Found shared_preferences → SharedPreferences

✨ 2 adapter(s) detected!
📝 Generated: lib/blackbox_adapters.dart
Enter fullscreen mode Exit fullscreen mode

Getting Started in 3 Steps

Step 1 — Add the package:

flutter pub add flutter_blackbox
Enter fullscreen mode Exit fullscreen mode

Step 2 — Generate your adapters:

dart run flutter_blackbox:init --generate
Enter fullscreen mode Exit fullscreen mode

Step 3 — Wrap your app:

import 'package:flutter/foundation.dart';
import 'package:flutter_blackbox/flutter_blackbox.dart';
import 'blackbox_adapters.dart'; // ← generated by CLI

final dio = Dio();

void main() {
  BlackBox.setup(
    httpAdapters: [DioBlackBoxAdapter(dio)],
    storageAdapters: [SharedPrefsStorageAdapter()],
    logAdapter: PrintLogAdapter(),
    trigger: const BlackBoxTrigger.floatingButton(),
    enabled: kDebugMode,
  );
  runApp(const BlackBoxOverlay(child: MyApp()));
}
Enter fullscreen mode Exit fullscreen mode

That's it. BlackBox observes your existing Dio instance via interceptors — your API code is completely untouched.

If you don't use Dio or any adapters, the Quick Start is even simpler. Just two lines and you get logs, crashes, FPS, widget rebuilds, device info, and QA reports immediately:

void main() {
  BlackBox.setup(
    trigger: const BlackBoxTrigger.floatingButton(),
    enabled: kDebugMode,
  );
  runApp(const BlackBoxOverlay(child: MyApp()));
}
Enter fullscreen mode Exit fullscreen mode

The Part That Saved My Team Hours

The QA Reports panel changed how we handle bug reports internally.

Instead of the painful back-and-forth I described at the start, QA testers now:

  1. Reproduce the bug
  2. Open BlackBox → QA tab
  3. Type the bug name
  4. Tap "Generate Report"
  5. Tap "Copy"
  6. Paste into the GitHub issue

The Markdown report includes the full device spec, OS version, network connectivity, every API call made in the session (with request/response bodies), the user's navigation journey (route changes + taps), recent logs, and any crash stack traces. All automatically collected.

Here's what the output looks like:

# Bug Report: User list not loading

**Device**: Pixel 6 Pro · Android 14 · API 34
**App**: v2.4.1 (build 47) · Debug
**Connectivity**: WiFi

## User Journey
1. App launched
2. Navigated to /home
3. Tapped "Users" tab
4. Navigated to /users

## Failed Network Requests
GET /api/v1/users → 401 Unauthorized (234ms)
  Authorization: Bearer [REDACTED]
  Response: {"error": "Token expired"}

## Recent Logs
[ERROR] Auth: Token validation failed — tag: AuthService
[INFO]  Navigation: Pushed /users — tag: Router

## Crash Log
None
Enter fullscreen mode Exit fullscreen mode

One tap. No Slack back-and-forth. No "can you send me the Proxyman export."


Observe Only, Never Modify

This was a deliberate design principle and one I'm proud of.

BlackBox hooks into your libraries through their built-in extension points. For Dio, it registers an Interceptor. For http, it wraps your existing Client. For Socket.IO, it uses socket.onAny(). For SharedPreferences, it reads via the existing API.

Your code doesn't change. BlackBox is a silent observer. If you remove the package tomorrow, nothing in your codebase breaks.


Widget Rebuild Tracker

This one is genuinely useful for performance work, not just debugging.

Toggle "AUTO ON" in the Rebuilds panel and BlackBox hooks into Flutter's debugPrintRebuildDirtyWidgets flag. Every widget that rebuilds gets counted. The panel shows a heat-colored ranking — the most frequently rebuilding widget is at the top, color-coded red.

You can spot a setState() in the wrong place in about 10 seconds. No profiler session needed.

For more surgical tracking, wrap specific widgets:

RebuildTracker(
  label: 'ProductCard',
  child: ProductCard(),
)
Enter fullscreen mode Exit fullscreen mode

RebuildTracker is eliminated by Dart's tree-shaker in release builds via kDebugMode. Zero production footprint.


API Mocking Without a Mock Server

The mocking panel lets you intercept any API call and return a local response, without touching your API client code or spinning up a mock server.

BlackBox.mock(
  pattern: '/api/v1/user/profile',
  method: 'GET',
  response: MockResponse(
    statusCode: 200,
    body: {'name': 'Alice', 'role': 'Admin'},
  ),
);
Enter fullscreen mode Exit fullscreen mode

You can also set a network throttle via the slider in the Mocking panel — simulate 3G, high latency, or 0 kbps to test your loading states and timeout handling without leaving your desk.


Crashlytics / Sentry Integration

If your team uses Firebase Crashlytics or Sentry, BlackBox can forward its captured telemetry to them automatically. Implement BlackBoxObserver:

class CrashlyticsObserver extends BlackBoxObserver {
  @override
  void onCrash(CrashEntry crash) {
    FirebaseCrashlytics.instance.recordError(
      crash.message,
      crash.stackTrace,
    );
  }

  @override
  void onNetworkError(NetworkRequest request) {
    // Forward failed requests to your monitoring tool
  }
}

BlackBox.setup(observers: [CrashlyticsObserver()]);
Enter fullscreen mode Exit fullscreen mode

If the CLI detects firebase_crashlytics in your pubspec.yaml, it auto-generates this observer for you. Every crash BlackBox captures gets forwarded to your Firebase dashboard, tagged for easy filtering.


Sensitive Data is Redacted by Default

The Storage Inspector auto-redacts values whose keys match patterns like password, token, secret, jwt, pin, auth, and 20+ more. They display as •••••••• and can't be copied or edited. You can customize the patterns per adapter or disable redaction entirely for internal dev-only builds.


Storage Inspector for Any Backend

The Storage Inspector isn't tied to SharedPreferences. Implement BlackBoxStorageAdapter for any key-value store:

class GetStorageAdapter extends BlackBoxStorageAdapter {
  final GetStorage _box;
  GetStorageAdapter(this._box);

  @override String get name => 'GetStorage';
  @override Future<Map<String, dynamic>> readAll() async => _box.getValues();
  @override Future<void> write(String key, dynamic value) async => _box.write(key, value);
  @override Future<void> delete(String key) async => _box.remove(key);
  @override Future<void> clear() async => _box.erase();
}
Enter fullscreen mode Exit fullscreen mode

Multiple adapters each get their own tab inside the panel. SharedPreferences and GetStorage side by side, both inspectable and editable.


What's in 0.6.x

The latest release added several quality-of-life features:

  • Mini HUD Mode — drag the floating button to the screen's bottom edge. It collapses into a persistent status bar showing live FPS, recent HTTP status codes, and crash count. Stays out of your way but always visible.
  • Swipe-to-Dismiss — swipe left-to-right on any network request or log entry to remove it.
  • HAR Export — copy all network requests as HAR 1.2 JSON, importable directly into Postman or Chrome DevTools.
  • Isolate JSON Parsing — large response bodies are parsed off the main thread so the overlay never janks on heavy payloads.
  • Glassmorphic UI — frosted-glass panels, gradient shines, blur effects, color-coded left accent borders on rows for instant status scanning.
  • Pull-to-Refresh — pull down on any list panel to refresh or reset data.
  • Haptic Feedback — tab switches and swipe actions provide tactile feedback.

Performance of the Overlay Itself

BlackBox is built to be fast even when your app is under load.

A few decisions made during development:

  • Lazy Panel Loading — panels are created only when first navigated to, not at startup. Initial memory footprint is ~60% lower than eager loading.
  • O(1) Network Lookups — the network store uses Map-based indexing, not linear scans.
  • FPS Callback via Engine Timestamp — the frame callback uses the engine-provided timestamp parameter instead of DateTime.now(), eliminating a syscall at 60fps.
  • Log Store Lazy Iteration — filtering returns Iterable, not List, avoiding unnecessary allocations.
  • Debounced Search — 300ms debounce on cross-panel search to prevent UI thread thrashing.

How to Install

dependencies:
  flutter_blackbox: ^0.6.2
Enter fullscreen mode Exit fullscreen mode

Then:

flutter pub add flutter_blackbox
dart run flutter_blackbox:init --generate
Enter fullscreen mode Exit fullscreen mode

Links:


Final Thought

The best debug tool is the one that's already in the build when something goes wrong. You don't always know in advance which API call will fail, which widget will rebuild 400 times, or what state your SharedPreferences will be in when QA files a ticket.

flutter_blackbox is designed to always be there, collecting silently in the background, zero cost in production, ready when you need it.

If you give it a try, I'd genuinely love to hear what you think — drop a comment, open a GitHub issue, or just hit the like button on pub.dev. Every piece of feedback has directly shaped what the package is today.


flutter_blackbox is MIT licensed and available on pub.dev. Contributions welcome — see CONTRIBUTING.md.

Tags: #flutter #dart #debugging #devtools #mobiledev #opensource

Top comments (0)