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
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
Getting Started in 3 Steps
Step 1 — Add the package:
flutter pub add flutter_blackbox
Step 2 — Generate your adapters:
dart run flutter_blackbox:init --generate
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()));
}
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()));
}
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:
- Reproduce the bug
- Open BlackBox → QA tab
- Type the bug name
- Tap "Generate Report"
- Tap "Copy"
- 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
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(),
)
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'},
),
);
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()]);
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();
}
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, notList, 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
Then:
flutter pub add flutter_blackbox
dart run flutter_blackbox:init --generate
Links:
- 📦 pub.dev: pub.dev/packages/flutter_blackbox
- 🐙 GitHub: github.com/jaiminraval2704/flutter_blackbox
- 📖 API Reference: pub.dev/documentation/flutter_blackbox/latest
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)