console_plus 2.0.2 copy "console_plus: ^2.0.2" to clipboard
console_plus: ^2.0.2 copied to clipboard

A Flutter plugin that provides an elegant in-app developer console with floating button.

example/lib/main.dart

import 'dart:async';

import 'package:console_plus/console_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

Future<void> main() async {
  await ConsolePlus.initApp(
    const ConsolePlusExampleApp(),
    interceptPrints: true,
    captureFlutterErrors: true,
    capturePlatformErrors: true,
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ConsolePlus Benchmark',
      theme: ThemeData.dark(useMaterial3: true),
      home: const BenchmarkPage(),
    );
  }
}

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

  @override
  State<BenchmarkPage> createState() => _BenchmarkPageState();
}

class _BenchmarkPageState extends State<BenchmarkPage> {
  static const String _payload = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
  bool _running = false;
  BenchmarkResult? _result1k;
  BenchmarkResult? _result5k;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await DebugLogConsole.setSavedQueryProfile('benchmark-example');
      DebugLogConsole.registerCustomAction(
        ConsoleCustomAction(
          id: 'benchmark_note',
          label: 'Add Benchmark Note',
          icon: Icons.note_add_outlined,
          onExecute: (context, filteredLogs) async {
            DebugLogConsole.addLog(
              "Benchmark note: filtered log count = ${filteredLogs.length}",
              type: LogType.info,
            );
          },
        ),
      );
      if (!mounted) return;
      FloatingDebugButton.show(context);
    });
  }

  Future<void> _runBenchmark(int totalLogs) async {
    if (_running) return;
    setState(() => _running = true);

    final timings = <FrameTiming>[];
    void frameCollector(List<FrameTiming> incoming) {
      timings.addAll(incoming);
    }

    SchedulerBinding.instance.addTimingsCallback(frameCollector);

    try {
      final firstUpdate = Completer<int>();
      late final VoidCallback listener;
      final expectedVisible = totalLogs > DebugLogConsole.maxLogCount
          ? DebugLogConsole.maxLogCount
          : totalLogs;
      final startedAt = DateTime.now();
      var firstSeen = false;

      listener = () {
        final current = DebugLogConsole.logs.length;
        if (!firstSeen && current > 0) {
          firstSeen = true;
          firstUpdate.complete(
            DateTime.now().difference(startedAt).inMilliseconds,
          );
        }
      };

      DebugLogConsole.clear();
      DebugLogConsole.logsNotifier.addListener(listener);

      final burstWatch = Stopwatch()..start();
      for (int i = 0; i < totalLogs; i++) {
        DebugLogConsole.addLog(
          "benchmark-$totalLogs-$i $_payload $_payload $_payload",
          type: i % 17 == 0
              ? LogType.error
              : (i % 7 == 0 ? LogType.warning : LogType.info),
        );
      }
      burstWatch.stop();

      final firstUpdateMs = await firstUpdate.future.timeout(
        const Duration(seconds: 5),
        onTimeout: () => -1,
      );

      final settleWatch = Stopwatch()..start();
      while (DebugLogConsole.logs.length < expectedVisible &&
          settleWatch.elapsed < const Duration(seconds: 6)) {
        await Future<void>.delayed(const Duration(milliseconds: 16));
      }
      settleWatch.stop();
      DebugLogConsole.logsNotifier.removeListener(listener);

      await Future<void>.delayed(const Duration(milliseconds: 600));
      final fps = _estimateFps(timings);
      final result = BenchmarkResult(
        totalLogs: totalLogs,
        mode: DebugLogConsole.highPerformanceMode
            ? 'High-performance'
            : 'Full-text',
        enqueueMs: burstWatch.elapsedMilliseconds,
        firstUiUpdateMs: firstUpdateMs,
        settleMs: settleWatch.elapsedMilliseconds,
        estimatedFps: fps,
      );

      setState(() {
        if (totalLogs == 1000) {
          _result1k = result;
        } else {
          _result5k = result;
        }
      });
    } finally {
      SchedulerBinding.instance.removeTimingsCallback(frameCollector);
      setState(() => _running = false);
    }
  }

  double _estimateFps(List<FrameTiming> timings) {
    if (timings.isEmpty) return 0;
    final totalMicros = timings
        .map((t) => t.totalSpan.inMicroseconds)
        .reduce((a, b) => a + b);
    final avgMicros = totalMicros / timings.length;
    if (avgMicros <= 0) return 0;
    return 1000000 / avgMicros;
  }

  @override
  Widget build(BuildContext context) {
    final highPerformanceMode = DebugLogConsole.highPerformanceMode;

    return Scaffold(
      appBar: AppBar(title: const Text('ConsolePlus Benchmark')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SwitchListTile(
              contentPadding: EdgeInsets.zero,
              title: const Text('High Performance Mode'),
              subtitle: Text(
                highPerformanceMode
                    ? 'Virtualized log list (faster, per-line selection)'
                    : 'Full-text mode (single selectable block)',
              ),
              value: highPerformanceMode,
              onChanged: _running
                  ? null
                  : (value) {
                      setState(() {
                        DebugLogConsole.highPerformanceMode = value;
                      });
                    },
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              children: [
                ElevatedButton(
                  onPressed: _running ? null : () => _runBenchmark(1000),
                  child: const Text('Run 1k Benchmark'),
                ),
                ElevatedButton(
                  onPressed: _running ? null : () => _runBenchmark(5000),
                  child: const Text('Run 5k Benchmark'),
                ),
                OutlinedButton(
                  onPressed: _running
                      ? null
                      : () {
                          setState(() {
                            _result1k = null;
                            _result5k = null;
                          });
                          DebugLogConsole.clear();
                        },
                  child: const Text('Reset'),
                ),
              ],
            ),
            const SizedBox(height: 12),
            if (_running) const LinearProgressIndicator(),
            const SizedBox(height: 16),
            _ResultCard(title: '1k Result', result: _result1k),
            const SizedBox(height: 10),
            _ResultCard(title: '5k Result', result: _result5k),
            const SizedBox(height: 12),
            const Text(
              'Tip: open the floating console while running to see real UI impact.',
              style: TextStyle(color: Colors.white70),
            ),
          ],
        ),
      ),
    );
  }
}

class _ResultCard extends StatelessWidget {
  final String title;
  final BenchmarkResult? result;

  const _ResultCard({required this.title, required this.result});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: result == null
            ? Text('$title: not run yet')
            : Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('$title (${result!.mode})'),
                  const SizedBox(height: 6),
                  Text('Logs: ${result!.totalLogs}'),
                  Text('Enqueue time: ${result!.enqueueMs} ms'),
                  Text('First UI update: ${result!.firstUiUpdateMs} ms'),
                  Text('Settle time: ${result!.settleMs} ms'),
                  Text(
                    'Estimated FPS: ${result!.estimatedFps.toStringAsFixed(1)}',
                  ),
                ],
              ),
      ),
    );
  }
}

class BenchmarkResult {
  final int totalLogs;
  final String mode;
  final int enqueueMs;
  final int firstUiUpdateMs;
  final int settleMs;
  final double estimatedFps;

  const BenchmarkResult({
    required this.totalLogs,
    required this.mode,
    required this.enqueueMs,
    required this.firstUiUpdateMs,
    required this.settleMs,
    required this.estimatedFps,
  });
}
7
likes
150
points
111
downloads

Documentation

Documentation
API reference

Publisher

verified publisherashishcodes.site

Weekly Downloads

A Flutter plugin that provides an elegant in-app developer console with floating button.

Repository (GitHub)

License

MIT (license)

Dependencies

file_saver, flutter, flutter_web_plugins, plugin_platform_interface, shared_preferences

More

Packages that depend on console_plus

Packages that implement console_plus