flutter_blackbox 0.6.1
flutter_blackbox: ^0.6.1 copied to clipboard
All-in-one in-app debug overlay for Flutter. Network inspector, API mocking, live logs, FPS monitor, rebuild tracker, storage inspector, crash reports, and QA tools.
// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║ Flutter BlackBox — Example App ║
// ║ ║
// ║ This example demonstrates every major feature of flutter_blackbox. ║
// ║ Run it, tap any button, and open the floating debug button to see ║
// ║ everything in action. ║
// ║ ║
// ║ Adapter file generated by: dart run flutter_blackbox:init --generate ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
import 'package:flutter_blackbox/flutter_blackbox.dart';
import 'blackbox_adapters.dart'; // generated by CLI
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Global Dio instance — BlackBox observes it silently via interceptor.
// Your API code stays 100% unchanged.
// ─────────────────────────────────────────────────────────────────────────────
final dio = Dio(BaseOptions(baseUrl: 'https://jsonplaceholder.typicode.com'));
void main() {
// ─────────────────────────────────────────────────────────────────────────
// STEP 1: Setup BlackBox
//
// This is the only setup code needed. Everything else is automatic.
// Pass only the adapters you need — unused ones are never installed.
// ─────────────────────────────────────────────────────────────────────────
BlackBox.setup(
// Network — observes all Dio requests/responses without touching your code.
// Supports multiple adapters: [DioBlackBoxAdapter(dio), HttpBlackBoxAdapter()]
httpAdapters: [DioBlackBoxAdapter(dio)],
// Logging — auto-captures debugPrint() and print() output.
// Shows in the Logs tab with level, tag, and timestamp.
logAdapter: PrintLogAdapter(),
// Storage — inspect SharedPreferences in the Storage tab.
// Add multiple adapters for different stores:
// storageAdapters: [SharedPrefsStorageAdapter(), GetStorageAdapter(box)]
storageAdapters: [SharedPrefsStorageAdapter()],
// Privacy — keys matching "password", "token", "secret", "jwt", etc.
// are masked as "••••••••" and can't be copied/edited.
// Set to false only for internal dev builds.
redactSensitiveData: true,
// Trigger — how to open the debug overlay.
// Options: .floatingButton(), .shake(), .hotkey(LogicalKeyboardKey.f12)
trigger: const BlackBoxTrigger.floatingButton(),
// Ignore noisy widgets from Rebuild Tracker (e.g., theme wrappers).
ignoredRebuildWidgets: const ['MyThemeWrapper'],
// Only active in debug/profile mode — zero cost in release builds.
enabled: kDebugMode,
);
// ─────────────────────────────────────────────────────────────────────────
// STEP 2: Setup API Mocks (optional)
//
// Intercept any API call and return fake data — perfect for offline dev,
// demos, or testing error states. Mocks are created in the Mocking tab
// or programmatically here.
// ─────────────────────────────────────────────────────────────────────────
BlackBox.mock(
pattern: '/api/v1/user/profile',
method: 'GET',
response: const MockResponse(
statusCode: 200,
body: {
'id': 1,
'name': 'Alice Johnson',
'email': 'alice@example.com',
'role': 'Admin',
},
),
);
// Mock a 500 error to test error handling in your app
BlackBox.mock(
pattern: '/api/v1/checkout',
method: 'POST',
response: const MockResponse(
statusCode: 500,
body: {'error': 'Internal Server Error'},
),
);
// ─────────────────────────────────────────────────────────────────────────
// STEP 3: Wrap your app with BlackBoxOverlay
//
// This adds the debug overlay on top of your app. The overlay renders
// above your entire widget tree so it never interferes with your layout.
// ─────────────────────────────────────────────────────────────────────────
runApp(const BlackBoxOverlay(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'BlackBox Example',
theme: ThemeData.dark(useMaterial3: true),
// ── Journey Tracking ──────────────────────────────────────────
// Add BlackBox.journeyObserver to navigatorObservers to track
// all route pushes/pops in the Journey tab. This helps reconstruct
// what the user did before a crash or bug.
navigatorObservers: [BlackBox.journeyObserver],
home: const HomeScreen(),
);
}
// ═════════════════════════════════════════════════════════════════════════════
// HOME SCREEN — Demo buttons for every BlackBox feature
// ═════════════════════════════════════════════════════════════════════════════
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('BlackBox Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// ── 1. LOGGING ───────────────────────────────────────────────
// BlackBox.log() lets you log messages with levels (info, warning,
// error, debug) and optional tags + data. All logs appear in the
// Logs tab with color-coded left borders by level.
//
// debugPrint() and print() are also auto-captured — no manual
// logging needed for those.
const _Section('📋 Logging'),
_Tile(
'Log info message',
Icons.info_outline,
() => BlackBox.log(
'User opened home screen',
level: LogLevel.info,
tag: 'Navigation',
),
),
_Tile(
'Log warning',
Icons.warning_amber,
() => BlackBox.log(
'Cache miss for key "user_profile"',
level: LogLevel.warning,
tag: 'Cache',
),
),
_Tile(
'Log error with data',
Icons.error_outline,
() => BlackBox.log(
'Payment failed',
level: LogLevel.error,
tag: 'Payment',
// Attach any Map data to the log entry — viewable in detail
data: {
'orderId': 'ORD-12345',
'amount': 99.99,
'currency': 'USD',
'errorCode': 'CARD_DECLINED',
},
),
),
_Tile(
'debugPrint (auto-captured)',
Icons.print,
() => debugPrint('[Auth] token refreshed at ${DateTime.now()}'),
),
// ── 2. NETWORK ───────────────────────────────────────────────
// Every Dio request is automatically logged in the Network tab.
// You can see: method, URL, status code, duration, headers,
// request/response bodies, timing bars, and copy as cURL.
//
// Swipe left-to-right on any entry to dismiss it.
// Pull down to refresh. Scroll down and tap ↑ to jump to top.
const SizedBox(height: 16),
const _Section('🌐 Network'),
_Tile(
'GET /posts (real API call)',
Icons.cloud_download,
() async {
try {
// This hits jsonplaceholder.typicode.com — a real API.
// Open Network tab to see the request, response, timing,
// and status code with a color-coded left accent border.
await dio.get<dynamic>('/posts?_limit=5');
debugPrint('✅ GET /posts succeeded');
} catch (e) {
debugPrint('❌ GET /posts failed: $e');
}
},
),
_Tile(
'GET /users (real API call)',
Icons.people,
() async {
try {
await dio.get<dynamic>('/users?_limit=3');
debugPrint('✅ GET /users succeeded');
} catch (e) {
debugPrint('❌ GET /users failed: $e');
}
},
),
_Tile(
'POST /posts (create)',
Icons.cloud_upload,
() async {
try {
// POST requests show the request body in the Network detail.
await dio.post<dynamic>('/posts', data: {
'title': 'BlackBox Test',
'body': 'Testing POST request capture',
'userId': 1,
});
debugPrint('✅ POST /posts succeeded');
} catch (e) {
debugPrint('❌ POST failed: $e');
}
},
),
_Tile(
'GET /api/v1/user/profile (mocked)',
Icons.person_pin,
() async {
try {
// This URL matches the mock we registered in main().
// The response comes from the MockResponse, not the network.
// The Network tab shows it as a normal response.
final res = await dio.get<dynamic>('/api/v1/user/profile');
debugPrint('🎭 Mocked response: ${res.data}');
} catch (_) {}
},
),
_Tile(
'POST /api/v1/checkout (mocked 500)',
Icons.error,
() async {
try {
// This mock returns a 500 error — useful for testing
// your app's error handling without a real server.
await dio.post<dynamic>('/api/v1/checkout', data: {'items': 3});
} catch (e) {
debugPrint('🎭 Mocked 500 error: $e');
}
},
),
// ── 3. STORAGE ───────────────────────────────────────────────
// The Storage tab shows all key-value pairs from your registered
// adapters. You can search, edit values inline, delete keys,
// and add new entries. Sensitive keys are auto-redacted.
const SizedBox(height: 16),
const _Section('💾 Storage'),
_Tile(
'Write sample preferences',
Icons.save,
() async {
final prefs = await SharedPreferences.getInstance();
// These will appear in the Storage tab immediately
await prefs.setString('username', 'john_doe');
await prefs.setBool('dark_mode', true);
await prefs.setInt('login_count', 42);
await prefs.setString('app_version', '2.1.0');
// These sensitive keys will be auto-redacted (shown as ••••••••)
await prefs.setString(
'auth_token', 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lk...');
await prefs.setString('user_password_hash', 'bcrypt\$2b\$12...');
await prefs.setString('api_secret', 'sk-1234567890abcdef');
debugPrint('✅ Sample preferences written — check Storage tab');
},
),
// ── 4. REBUILD TRACKING ──────────────────────────────────────
// The Rebuild tab shows which widgets rebuild the most often.
// Two modes:
//
// Automatic: Toggle "AUTO ON" in the Rebuild tab or call
// BlackBox.startRebuildTracking(). Tracks ALL widget rebuilds
// using Flutter's debugPrintRebuildDirtyWidgets hook.
//
// Manual: Wrap specific widgets with RebuildTracker(label, child).
// Zero cost in release builds (uses kDebugMode).
//
// Pull down on the Rebuild tab to reset all counts.
const SizedBox(height: 16),
const _Section('🔄 Rebuild Tracking'),
const _Tile(
'Start auto-tracking',
Icons.visibility,
BlackBox.startRebuildTracking,
),
const _Tile(
'Stop auto-tracking',
Icons.visibility_off,
BlackBox.stopRebuildTracking,
),
// ── Manual RebuildTracker wrapper example ──
// This widget's rebuild count appears in the Rebuild tab
// under the label "ExampleCard".
RebuildTracker(
label: 'ExampleCard',
child: _Tile(
'I am tracked (tap to force rebuild)',
Icons.refresh,
() => (context as Element).markNeedsBuild(),
),
),
// ── 5. CRASH HANDLING ────────────────────────────────────────
// BlackBox auto-catches:
// • Flutter framework errors (e.g., RenderBox overflow)
// • Unhandled async exceptions
// • Zone errors
// All appear in the Crashes tab with stack traces.
// You can also throw manually to test:
const SizedBox(height: 16),
const _Section('🐛 Crashes'),
_Tile(
'Trigger a test exception',
Icons.bug_report,
() {
// This will be caught by BlackBox and shown in the Crashes tab.
// Your app won't crash — BlackBox handles it gracefully.
try {
throw Exception('Test crash from example app');
} catch (e, s) {
FlutterError.reportError(FlutterErrorDetails(
exception: e,
stack: s,
library: 'example app',
context: ErrorDescription('testing crash capture'),
));
debugPrint('💥 Exception reported — check Crashes tab');
}
},
),
// ── 6. OVERLAY CONTROL ───────────────────────────────────────
// You can open/close/toggle the overlay programmatically.
// Useful for: custom trigger buttons, automated testing,
// or opening after a specific event.
const SizedBox(height: 16),
const _Section('🎮 Overlay Control'),
const _Tile('Open BlackBox overlay', Icons.bug_report, BlackBox.open),
const _Tile('Close BlackBox overlay', Icons.close, BlackBox.close),
// ── 7. TIPS ──────────────────────────────────────────────────
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('💡 Tips',
style:
TextStyle(fontWeight: FontWeight.w700, fontSize: 13)),
SizedBox(height: 8),
Text(
'• Tap the floating 🐞 button to open the overlay\n'
'• Drag the button to bottom edge → Mini HUD mode\n'
'• Drag the top handle to resize the panel\n'
'• Double-tap the handle to snap between 50%/85%\n'
'• Swipe left-to-right on logs/network to dismiss\n'
'• Long-press any log entry to copy it\n'
'• Pull down on any list to refresh\n'
'• Use the QA tab to generate bug reports with screenshots',
style: TextStyle(
fontSize: 12, color: Colors.white54, height: 1.6),
),
],
),
),
const SizedBox(height: 16),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// UI Components (not part of BlackBox — just for this example app)
// ─────────────────────────────────────────────────────────────────────────────
class _Section extends StatelessWidget {
const _Section(this.title);
final String title;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: Colors.white.withValues(alpha: 0.4),
letterSpacing: 1.2,
),
),
);
}
class _Tile extends StatelessWidget {
const _Tile(this.title, this.icon, this.onTap);
final String title;
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) => Card(
margin: const EdgeInsets.only(bottom: 6),
child: ListTile(
leading: Icon(icon, size: 20),
title: Text(title, style: const TextStyle(fontSize: 13)),
trailing: const Icon(Icons.chevron_right, size: 16),
onTap: onTap,
dense: true,
),
);
}