bug_lens 1.1.1 copy "bug_lens: ^1.1.1" to clipboard
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.

example/lib/main.dart

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)),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
150
points
143
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#debugging #developer-tools #logging #network-inspector #bug-reporter

License

MIT (license)

Dependencies

flutter

More

Packages that depend on bug_lens