flutter_app_inspector 0.1.0 copy "flutter_app_inspector: ^0.1.0" to clipboard
flutter_app_inspector: ^0.1.0 copied to clipboard

A developer observability and runtime inspection layer for Flutter applications.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_inspector/flutter_app_inspector.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize AppInspector with debug/profile mode defaults
  AppInspector.initialize(
    config: const AppInspectorConfig(
      enabled: kDebugMode || kProfileMode,
      consoleLogging: true,
      logLevel: InspectorLogLevel.info,
      overlayMode: InspectorOverlayMode.compact,
      overlayPosition: InspectorOverlayPosition.topRight,
      widgetMonitoring: true,
    ),
  );

  runApp(
    const AppInspectorOverlay(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter App Inspector Demo',
      debugShowCheckedModeBanner: false,
      navigatorObservers: [AppInspector.navigatorObserver],
      theme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: Colors.indigo,
        useMaterial3: true,
      ),
      home: const DemoHomePage(),
    );
  }
}

class DemoHomePage extends StatefulWidget {
  const DemoHomePage({super.key});

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  int _counter = 0;
  int _spikeCounter = 0;
  final List<Uint8List> _retainedBuffers = [];
  String _statusMessage = 'Inspector overlay is floating on screen top-right.';

  void _generateHeavyWork() {
    setState(() {
      _statusMessage = 'Executing CPU heavy work loop on UI thread...';
    });

    final stopwatch = Stopwatch()..start();
    double result = 0.0;
    for (int i = 0; i < 20000000; i++) {
      result += (i * 0.00001);
    }
    stopwatch.stop();

    setState(() {
      _counter++;
      _statusMessage =
          'Heavy work completed in ${stopwatch.elapsedMilliseconds}ms. (Result: ${result.toStringAsFixed(1)})';
    });
  }

  void _triggerJank() {
    setState(() {
      _statusMessage =
          'Simulating synthetic jank delay on UI thread (150ms)...';
    });

    sleepSync(const Duration(milliseconds: 150));

    setState(() {
      _counter++;
      _statusMessage =
          'Jank triggered! Check overlay frame duration and jank count.';
    });
  }

  void _triggerRebuildSpike() {
    setState(() {
      _statusMessage = 'Simulating rapid widget rebuild surge...';
    });

    // Rapidly trigger state rebuilds to simulate a widget rebuild spike
    for (int i = 0; i < 25; i++) {
      setState(() {
        _spikeCounter++;
      });
    }

    setState(() {
      _statusMessage = 'Widget rebuild spike simulated! Check HUD Hotspots.';
    });
  }

  void _triggerSlowWidgetBuild() {
    setState(() {
      _statusMessage = 'Simulating slow widget build pass (24.1ms)...';
    });

    AppInspector.trackWidgetRebuild(
      'ProductList',
      const Duration(microseconds: 24100),
    );

    setState(() {
      _statusMessage =
          'Slow widget build recorded! Check HUD Potential Slow Widget alert.';
    });
  }

  void _navigateToDetailsScreen() {
    Navigator.push(
      context,
      MaterialPageRoute(
        settings: const RouteSettings(name: '/details'),
        builder: (context) => const DemoDetailsScreen(),
      ),
    );
  }

  void _simulateImageLoad() {
    AppInspector.trackImageLoad(
      urlOrKey: 'assets/sample_banner.png',
      state: ImageLoadState.cacheMiss,
      width: 1200,
      height: 800,
      displayWidth: 400,
      displayHeight: 267,
      loadDuration: const Duration(milliseconds: 45),
    );
    setState(() {
      _statusMessage = 'Recorded image load (1200x800, cache miss)';
    });
  }

  void _simulateOversizedImage() {
    AppInspector.trackImageLoad(
      urlOrKey: 'hero_full_res_photo.png',
      state: ImageLoadState.cacheHit,
      width: 6000,
      height: 4000,
      displayWidth: 400,
      displayHeight: 280,
      loadDuration: const Duration(milliseconds: 12),
    );
    setState(() {
      _statusMessage =
          'Recorded oversized image alert (6000x4000 -> 400x280 display)';
    });
  }

  void _recordSingleError() {
    try {
      throw StateError('Cannot modify unmodifiable list element');
    } catch (e, stack) {
      AppInspector.recordError(e, stackTrace: stack);
    }
    setState(() {
      _statusMessage = 'Recorded StateError in Error & Exception Monitor';
    });
  }

  void _simulateRepeatedErrorSurge() {
    for (int i = 0; i < 27; i++) {
      AppInspector.recordError(
        Exception('SocketException: Connection refused'),
        severity: InspectorSeverity.warning,
      );
    }
    setState(() {
      _statusMessage = 'Recorded 27x repeated SocketException surge';
    });
  }

  void _analyzePerformance() {
    final report = AppInspector.analyzePerformance();
    debugPrint(
        '\n=== WHY IS MY APP SLOW? ===\n${report.formattedSummary}\n===========================\n');
    setState(() {
      _statusMessage =
          'Ran "Why Is My App Slow?" analysis (Score: ${report.score.overall.round()}/100)';
    });
  }

  void _simulateSlowNetworkRequest() {
    AppInspector.store.add(
      AppInspectorEvent(
        id: 'net_${DateTime.now().millisecondsSinceEpoch}',
        type: InspectorEventType.network,
        timestamp: DateTime.now(),
        name: '/api/v1/products',
        severity: InspectorSeverity.warning,
        metadata: {
          'durationMs': 421,
          'url': '/api/v1/products',
        },
      ),
    );
    setState(() {
      _statusMessage = 'Recorded slow network request (/api/v1/products 421ms)';
    });
  }

  void _simulateAppStartup() {
    AppInspector.store.add(
      AppInspectorEvent(
        id: 'start_${DateTime.now().millisecondsSinceEpoch}',
        type: InspectorEventType.startup,
        timestamp: DateTime.now(),
        name: 'Cold Start Initialization',
        severity: InspectorSeverity.info,
        metadata: {
          'durationMs': 1250,
        },
      ),
    );
    setState(() {
      _statusMessage = 'Recorded App Cold Startup timing event (1250ms)';
    });
  }

  void _generateMemoryActivity() {
    setState(() {
      _statusMessage = 'Allocating 20 MB Uint8List buffer...';
    });

    final buffer = Uint8List(20 * 1024 * 1024);
    for (int i = 0; i < buffer.length; i += 1024) {
      buffer[i] = i % 256;
    }
    _retainedBuffers.add(buffer);

    final totalMb = (_retainedBuffers.length * 20.0).toStringAsFixed(0);
    setState(() {
      _statusMessage = 'Allocated ${totalMb}MB of retained memory buffers.';
    });
  }

  Future<void> _runCustomTracker() async {
    setState(() {
      _statusMessage = 'Running tracked custom operation...';
    });

    await AppInspector.measure('Checkout Process', () async {
      await Future.delayed(const Duration(milliseconds: 800));
    });

    setState(() {
      _statusMessage = 'Custom operation recorded in event store (800ms).';
    });
  }

  Future<void> _runNetworkTracker() async {
    setState(() {
      _statusMessage = 'Simulating network request tracking...';
    });

    final tracker = AppInspector.startNetworkRequest(
        method: 'GET', url: '/api/v1/products');
    await Future.delayed(const Duration(milliseconds: 1200));
    tracker.complete(statusCode: 200, responseSize: 48500);

    setState(() {
      _statusMessage = 'Network request recorded (1200ms latency warning).';
    });
  }

  void _resetMetrics() {
    _retainedBuffers.clear();
    _spikeCounter = 0;
    AppInspector.reset();
    setState(() {
      _statusMessage = 'Reset all metrics and cleared retained memory buffers.';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('⚡ App Inspector Demo'),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh_rounded),
            tooltip: 'Reset Inspector Metrics',
            onPressed: _resetMetrics,
          ),
        ],
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Tracked Demo Widget: ProductList Card
              InspectorWidgetTracker(
                name: 'ProductList',
                child: Card(
                  color: const Color(0xFF1E2838),
                  elevation: 2,
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      children: [
                        const Icon(Icons.speed_rounded,
                            size: 36, color: Color(0xFF81D4FA)),
                        const SizedBox(height: 8),
                        const Text(
                          'Developer Observability & HUD Demo',
                          style: TextStyle(
                              fontSize: 16, fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 4),
                        Text(
                          _statusMessage,
                          textAlign: TextAlign.center,
                          style: const TextStyle(
                              fontSize: 12, color: Color(0xFFB0BEC5)),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Expanded(
                child: ListView(
                  children: [
                    // Tracked Demo Widget: ProductCard Action
                    InspectorWidgetTracker(
                      name: 'ProductCard',
                      child: _DemoActionButton(
                        icon: Icons.widgets_rounded,
                        color: Colors.cyanAccent,
                        title: 'Simulate Rebuild Surge (Spike)',
                        subtitle:
                            'Triggers 25 rapid rebuilds on ProductList & ProductCard',
                        onPressed: _triggerRebuildSpike,
                      ),
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.hourglass_bottom_rounded,
                      color: Colors.deepOrangeAccent,
                      title: 'Simulate Slow Widget Build (24ms)',
                      subtitle:
                          'Records a 24.1ms build pass exceeding 16.7ms frame budget',
                      onPressed: _triggerSlowWidgetBuild,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.alt_route_rounded,
                      color: Colors.tealAccent,
                      title: 'Test Route Transition (/details)',
                      subtitle:
                          'Pushes /details route & records navigation transition duration',
                      onPressed: _navigateToDetailsScreen,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.image_rounded,
                      color: Colors.purpleAccent,
                      title: 'Simulate Image Load (1200x800)',
                      subtitle:
                          'Records an image load metric with cache miss duration',
                      onPressed: _simulateImageLoad,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.photo_size_select_large_rounded,
                      color: Colors.deepOrangeAccent,
                      title: 'Simulate Oversized Image (6000x4000)',
                      subtitle:
                          'Triggers ⚠️ Large Image alert (~91.5 MB estimated decoded RAM)',
                      onPressed: _simulateOversizedImage,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.warning_amber_rounded,
                      color: Colors.amberAccent,
                      title: 'Record Single Exception (StateError)',
                      subtitle:
                          'Records a StateError exception with sanitized stack trace',
                      onPressed: _recordSingleError,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.error_outline_rounded,
                      color: Colors.redAccent,
                      title:
                          'Simulate Repeated Error Surge (27x SocketException)',
                      subtitle:
                          'Triggers ⚠️ Repeated Error alert (SocketException 27 occurrences)',
                      onPressed: _simulateRepeatedErrorSurge,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.analytics_rounded,
                      color: Colors.lightBlueAccent,
                      title: 'Analyze Performance ("Why Is My App Slow?")',
                      subtitle:
                          'Runs full diagnostic evaluation across frames, rebuilds, images & errors',
                      onPressed: _analyzePerformance,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.wifi_tethering_off_rounded,
                      color: Colors.orangeAccent,
                      title: 'Simulate Slow Network Request (/api/v1/products)',
                      subtitle:
                          'Records a 421ms network latency event in store',
                      onPressed: _simulateSlowNetworkRequest,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.power_settings_new_rounded,
                      color: Colors.lightGreenAccent,
                      title: 'Simulate App Cold Startup Timing (1250ms)',
                      subtitle:
                          'Records a 1250ms cold start initialization event',
                      onPressed: _simulateAppStartup,
                    ),
                    const SizedBox(height: 10),

                    _DemoActionButton(
                      icon: Icons.flash_on_rounded,
                      color: Colors.amber,
                      title: 'Generate Heavy Work',
                      subtitle:
                          'Runs a 20M iteration loop synchronously on UI thread',
                      onPressed: _generateHeavyWork,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.report_problem_rounded,
                      color: Colors.orangeAccent,
                      title: 'Trigger Jank (150ms delay)',
                      subtitle:
                          'Forces a frame drop exceeding 16.7ms target budget',
                      onPressed: _triggerJank,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.memory_rounded,
                      color: Colors.lightBlueAccent,
                      title: 'Generate Memory Activity',
                      subtitle:
                          'Allocates and retains 20 MB byte buffer in memory',
                      onPressed: _generateMemoryActivity,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.timer_rounded,
                      color: Colors.greenAccent,
                      title: 'Run Custom Operation Tracking',
                      subtitle: 'Uses AppInspector.measure() for custom timing',
                      onPressed: _runCustomTracker,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.http_rounded,
                      color: Colors.purpleAccent,
                      title: 'Simulate Slow Network Request',
                      subtitle: 'Uses AppInspector.startNetworkRequest()',
                      onPressed: _runNetworkTracker,
                    ),
                    const SizedBox(height: 10),
                    _DemoActionButton(
                      icon: Icons.restart_alt_rounded,
                      color: Colors.redAccent,
                      title: 'Reset Metrics & Memory',
                      subtitle: 'Clears event buffer and memory allocations',
                      onPressed: _resetMetrics,
                    ),
                  ],
                ),
              ),
              Container(
                padding: const EdgeInsets.all(12.0),
                decoration: BoxDecoration(
                  color: Colors.black38,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      'Counter: $_counter | Spikes: $_spikeCounter',
                      style: const TextStyle(
                          fontWeight: FontWeight.bold, fontSize: 13),
                    ),
                    Text(
                      'Retained Buffers: ${_retainedBuffers.length}',
                      style:
                          const TextStyle(fontSize: 12, color: Colors.white70),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _DemoActionButton extends StatelessWidget {
  final IconData icon;
  final Color color;
  final String title;
  final String subtitle;
  final VoidCallback onPressed;

  const _DemoActionButton({
    required this.icon,
    required this.color,
    required this.title,
    required this.subtitle,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: const Color(0xFF1E2838),
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
        alignment: Alignment.centerLeft,
      ),
      onPressed: onPressed,
      child: Row(
        children: [
          CircleAvatar(
            backgroundColor: color.withValues(alpha: 0.2),
            radius: 18,
            child: Icon(icon, color: color, size: 20),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  style: const TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 13),
                ),
                const SizedBox(height: 2),
                Text(
                  subtitle,
                  style: const TextStyle(color: Colors.white54, fontSize: 11),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

void sleepSync(Duration duration) {
  final end = DateTime.now().add(duration);
  while (DateTime.now().isBefore(end)) {
    // Synchronous busy wait
  }
}

class DemoDetailsScreen extends StatelessWidget {
  const DemoDetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Details Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.alt_route_rounded,
                size: 48, color: Colors.tealAccent),
            const SizedBox(height: 12),
            const Text(
              'Route /details loaded successfully!',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            const Text(
              'Navigation transition duration was measured by InspectorNavigatorObserver.',
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.white70, fontSize: 12),
            ),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              icon: const Icon(Icons.arrow_back_rounded),
              label: const Text('Pop Back to Home'),
              onPressed: () => Navigator.pop(context),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
69
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A developer observability and runtime inspection layer for Flutter applications.

Repository (GitHub)
View/report issues

Topics

#performance #inspector #observability #devtools #debug

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_app_inspector