bug_lens 1.1.1
bug_lens: ^1.1.1 copied to clipboard
A unified in-app QA reporter and developer debugging toolkit for Flutter. Inspect UI widgets, network, logs, events, errors, and capture annotated screenshots directly inside your app.
import 'package:bug_lens/bug_lens.dart';
import 'package:flutter/material.dart';
// π 1-Line Setup in main(): Automatically initializes binding, BugLens, error hooks, and print interception!
void main() {
BugLens.run(
() => const BugLensDemoApp(),
environment: BugLensEnvironment.dev,
);
}
class BugLensDemoApp extends StatelessWidget {
const BugLensDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BugLens Showcase App',
debugShowCheckedModeBanner: false,
// π 1-Line Setup in MaterialApp: Injects BugLens overlay, screenshot capturer, video recorder, & inspector
builder: BugLens.builder(),
navigatorObservers: [BugLens.navigatorObserver],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6366F1),
brightness: Brightness.dark,
),
scaffoldBackgroundColor: const Color(0xFF0B1120),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _orderCount = 3;
bool _causeOverflow = false;
void _simulateSuccessfulApiCall() async {
// Standard HTTP tracker or Dio interceptor can be used without manual logging
const tracker = BugLens.httpTracker;
final reqId = tracker.trackRequest(
url: 'https://api.store.example.com/v1/products',
method: 'GET',
headers: {'Accept': 'application/json'},
);
// Standard print statements are automatically intercepted into BugLens structured logs!
print('[Network] Fetching product catalog from remote server...');
await Future.delayed(const Duration(milliseconds: 250));
tracker.trackResponse(
requestId: reqId,
statusCode: 200,
statusMessage: 'OK',
body: {
'total': 2,
'items': [
{'id': 'p1', 'name': 'MacBook Pro 16"', 'price': 2499.00},
{'id': 'p2', 'name': 'Noise Cancelling Headphones', 'price': 349.00},
],
},
);
print('[Network] Product catalog successfully loaded (2 items)');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('GET /products (200 OK) captured via Interceptor!'),
backgroundColor: Color(0xFF10B981),
),
);
}
void _simulateFailed500ApiCall() async {
const tracker = BugLens.httpTracker;
final reqId = tracker.trackRequest(
url: 'https://api.store.example.com/v1/payment/charge',
method: 'POST',
body: {'amount': 2848.00, 'currency': 'USD', 'cardNumber': '4111***4444'},
);
print('[Payment] Processing charge for \$2848.00...');
await Future.delayed(const Duration(milliseconds: 500));
tracker.trackError(
requestId: reqId,
statusCode: 500,
error: 'PaymentGatewayTimeout: Downstream acquirer failed to respond within 30000ms',
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('POST /payment/charge (500 ERROR) logged in BugLens!'),
backgroundColor: Color(0xFFEF4444),
),
);
}
void _simulateUnhandledException() {
print('[Error] Triggering intentional exception to test auto-screenshot on error...');
try {
dynamic nullObject;
nullObject.someNonExistentMethod();
} catch (e, stack) {
BugLens.recordError(
e,
stackTrace: stack,
screen: '/home',
errorType: 'NoSuchMethodError',
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Exception caught and recorded: $e'),
backgroundColor: const Color(0xFFEF4444),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Row(
children: [
Icon(Icons.lens, color: Color(0xFF6366F1)),
SizedBox(width: 8),
Text('BugLens Showcase', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
actions: [
IconButton(
icon: const Icon(Icons.videocam),
tooltip: 'Record Screen Video Clip',
onPressed: () {
BugLens.startRecording();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Screen Recording Started! Tap "Stop & Report" when done.')),
);
},
),
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Live Widget Inspector',
onPressed: () => BugLens.inspect(),
),
IconButton(
icon: const Icon(Icons.dashboard_customize),
tooltip: 'Open Dev Console',
onPressed: () => BugLens.open(),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Banner
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF4F46E5), Color(0xFF7C3AED)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: const Color(0xFF6366F1).withValues(alpha: 0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'1-Line Setup Active: QA & Dev HUD Ready π',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
const Text(
'Tap the floating bug button, press Ctrl+Shift+B, or tap the video recorder icon in the app bar.',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF4F46E5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
),
icon: const Icon(Icons.bug_report, size: 16),
label: const Text('Report a Bug', style: TextStyle(fontWeight: FontWeight.bold)),
onPressed: () => BugLens.report(),
),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: const BorderSide(color: Colors.white60),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
),
icon: const Icon(Icons.touch_app, size: 16),
label: const Text('Inspect UI'),
onPressed: () => BugLens.inspect(),
),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: const BorderSide(color: Colors.white60),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
),
icon: const Icon(Icons.videocam, size: 16),
label: const Text('Record Video'),
onPressed: () => BugLens.startRecording(),
),
],
),
],
),
),
const SizedBox(height: 24),
// Diagnostic Simulation Actions
const Text(
'Simulate Diagnostics & Network Interceptors',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 12),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 2.2,
children: [
_actionCard(
icon: Icons.check_circle_outline,
color: const Color(0xFF10B981),
title: 'GET /products (200)',
subtitle: 'Captured via Interceptor',
onTap: _simulateSuccessfulApiCall,
),
_actionCard(
icon: Icons.error_outline,
color: const Color(0xFFEF4444),
title: 'POST /charge (500)',
subtitle: 'Captured via Interceptor',
onTap: _simulateFailed500ApiCall,
),
_actionCard(
icon: Icons.warning_amber,
color: const Color(0xFFF59E0B),
title: 'Throw Exception',
subtitle: 'Auto-captures screenshot',
onTap: _simulateUnhandledException,
),
_actionCard(
icon: Icons.text_snippet_outlined,
color: const Color(0xFF3B82F6),
title: 'print() Interception',
subtitle: 'Auto-logs to BugLens',
onTap: () {
print('User tapped on action card at ${DateTime.now()}');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Console print() captured automatically!')),
);
},
),
],
),
const SizedBox(height: 24),
// Intentional Bugs Showcase
const Text(
'Intentional UI & State Glitches (Test Reporter)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF334155)),
),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.shopping_cart, color: Color(0xFF6366F1)),
title: const Text('Order Summary Item', style: TextStyle(color: Colors.white)),
subtitle: Text('Current Count: $_orderCount items', style: const TextStyle(color: Color(0xFF94A3B8))),
trailing: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF6366F1)),
onPressed: () {
setState(() => _orderCount++);
BugLens.state('CartState', {'itemCount': _orderCount, 'lastUpdated': DateTime.now().toIso8601String()});
},
child: const Text('Add Item', style: TextStyle(color: Colors.white)),
),
),
const Divider(color: Color(0xFF334155)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Trigger Visual UI Overflow glitch:', style: TextStyle(color: Colors.white, fontSize: 13)),
Switch(
value: _causeOverflow,
activeThumbColor: const Color(0xFFEF4444),
onChanged: (val) => setState(() => _causeOverflow = val),
),
],
),
if (_causeOverflow) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
color: Colors.red.withValues(alpha: 0.2),
child: const Row(
children: [
Text(
'OVERFLOW BUG: This text is intentionally way too long for a single row without flexible or wrap to demonstrate UI inspector hit test bounds',
style: TextStyle(color: Colors.redAccent, fontSize: 12),
),
],
),
),
],
],
),
),
const SizedBox(height: 24),
// Navigation Route Tests
const Text(
'Navigation & Routes',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF334155),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
icon: const Icon(Icons.receipt_long, size: 18),
label: const Text('Go to /orders'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: '/orders'),
builder: (ctx) => const OrdersScreen(),
),
);
},
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF334155),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
icon: const Icon(Icons.person, size: 18),
label: const Text('Go to /profile'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: '/profile'),
builder: (ctx) => const ProfileScreen(),
),
);
},
),
),
],
),
],
),
),
);
}
Widget _actionCard({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF334155)),
),
child: Row(
children: [
Icon(icon, color: color, size: 22),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
subtitle,
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 10),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
}
class OrdersScreen extends StatelessWidget {
const OrdersScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Orders History (/orders)'),
),
body: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: 4,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF334155)),
),
child: Row(
children: [
const Icon(Icons.local_shipping, color: Color(0xFF6366F1)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Order #100${index + 1}',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
Text('Placed on 2026-08-${28 - index}',
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12)),
],
),
),
Text(
'\$${(index + 1) * 129}.99',
style: const TextStyle(color: Color(0xFF10B981), fontWeight: FontWeight.bold),
),
],
),
);
},
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('User Profile (/profile)'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const CircleAvatar(
radius: 40,
backgroundColor: Color(0xFF6366F1),
child: Icon(Icons.person, size: 40, color: Colors.white),
),
const SizedBox(height: 16),
const Text(
'QA Test Engineer',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
const Text(
'tester@buglens.dev',
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 13),
),
const SizedBox(height: 24),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFFEF4444)),
onPressed: () {
print('[Auth] User signed out from /profile');
Navigator.pop(context);
},
child: const Text('Sign Out', style: TextStyle(color: Colors.white)),
),
],
),
),
);
}
}