Skip to main content

flutter_debug_tracker 0.2.0 copy "flutter_debug_tracker: ^0.2.0" to clipboard
flutter_debug_tracker: ^0.2.0 copied to clipboard

In-app debug tool for Flutter on Android and iOS: FPS monitor, network inspector, logs, Firebase Analytics viewer and FCM push notification tester.

example/lib/main.dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_debug_tracker/flutter_debug_tracker.dart';

void main() {
  DebugTracker.runZoned(
    () => runApp(const DemoApp()),
    config: const DebugTrackerConfig(
      customInfo: <String, String>{'App': 'Demo', 'Flavor': 'dev'},
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'flutter_debug_tracker demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: Colors.indigo),
      navigatorObservers: <NavigatorObserver>[DebugTracker.observer],
      builder: DebugTracker.builder(),
      routes: <String, WidgetBuilder>{
        '/': (_) => const HomePage(),
        '/details': (_) => const DetailsPage(),
      },
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final HttpClient _client = HttpClient();
  final List<List<int>> _retained = <List<int>>[];
  String _status = 'Tap a button, then open the bubble.';

  StreamSubscription<TestNotification>? _taps;

  @override
  void initState() {
    super.initState();
    // Without FCM, test notification taps arrive here.
    _taps = DebugTracker.instance.notifications.onTap.listen(
      (n) =>
          setState(() => _status = 'Tapped notification: ${n.title} ${n.data}'),
    );
  }

  @override
  void dispose() {
    _taps?.cancel();
    _client.close();
    super.dispose();
  }

  Future<void> _request(String method, String url, {Object? body}) async {
    try {
      final request = await _client.openUrl(method, Uri.parse(url));
      if (body != null) {
        request.headers.contentType = ContentType.json;
        request.write(jsonEncode(body));
      }
      final response = await request.close();
      final text = await utf8.decoder.bind(response).join();
      setState(
        () => _status = '$method ${response.statusCode} · ${text.length} chars',
      );
    } catch (error) {
      setState(() => _status = 'Failed: $error');
    }
  }

  void _jank() {
    // Blocks the UI thread for ~300 ms to produce janky frames.
    final watch = Stopwatch()..start();
    var x = 0.0;
    while (watch.elapsedMilliseconds < 300) {
      x += math.sqrt(x + 1);
    }
    setState(() => _status = 'Blocked UI thread (${x.toStringAsFixed(0)})');
  }

  // Sends exactly what firebase_analytics' FirebaseAnalytics.instance
  // .logEvent() sends to the native SDK, so the demo runs without a Firebase
  // project. In a real app, just call FirebaseAnalytics as usual.
  Future<void> _firebaseLogEvent() async {
    const channel = BasicMessageChannel<Object?>(
      'dev.flutter.pigeon.firebase_analytics_platform_interface.'
      'FirebaseAnalyticsHostApi.logEvent',
      StandardMessageCodec(),
    );
    await channel.send(<Object?>[
      <String, Object?>{
        'eventName': 'add_to_cart',
        'parameters': <String, Object?>{
          'item_id': 'sku-42',
          'price': 19.99,
          'quantity': 1,
        },
      },
    ]);
    setState(() => _status = 'Sent Firebase add_to_cart');
  }

  Future<void> _trace() async {
    await DebugTracker.trace('fake database load', () async {
      await Future<void>.delayed(const Duration(milliseconds: 420));
    });
    setState(() => _status = 'Trace recorded');
  }

  @override
  Widget build(BuildContext context) {
    final actions = <(String, VoidCallback)>[
      (
        'GET JSON',
        () => _request('GET', 'https://jsonplaceholder.typicode.com/todos/1'),
      ),
      (
        'POST JSON',
        () => _request(
          'POST',
          'https://jsonplaceholder.typicode.com/posts',
          body: <String, Object>{'title': 'hello', 'userId': 1},
        ),
      ),
      ('GET 404', () => _request('GET', 'https://httpbin.org/status/404')),
      ('Unreachable host', () => _request('GET', 'https://nope.invalid/')),
      ('print()', () => print('Hello from print at ${DateTime.now()}')),
      ('debugPrint()', () => debugPrint('Hello from debugPrint')),
      (
        'Warning log',
        () => DebugTracker.log(
          'Token expires soon',
          level: LogLevel.warning,
          tag: 'auth',
        ),
      ),
      ('Throw async error', () => Future<void>.error(StateError('Boom'))),
      ('Jank 300 ms', _jank),
      ('Trace 420 ms', _trace),
      (
        'Allocate 20 MB',
        () => setState(() {
          _retained.add(List<int>.filled(20 * 1024 * 1024 ~/ 8, 1));
          _status = 'Retained ${_retained.length * 20} MB';
        }),
      ),
      ('Firebase logEvent', _firebaseLogEvent),
      (
        'Test notification (5 s)',
        () => DebugTracker.sendTestNotification(
          title: 'Order shipped',
          body: 'Your order #1042 is on its way',
          data: <String, String>{'orderId': '1042'},
          delay: const Duration(seconds: 5),
        ),
      ),
      (
        'Custom analytics',
        () => DebugTracker.logAnalyticsEvent(
          'signup_started',
          provider: 'mixpanel',
          parameters: <String, Object?>{'source': 'demo'},
        ),
      ),
      ('Open details', () => Navigator.of(context).pushNamed('/details')),
    ];

    return Scaffold(
      appBar: AppBar(title: const Text('Debug Tracker demo')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          Text(_status, style: Theme.of(context).textTheme.bodyLarge),
          const SizedBox(height: 16),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: <Widget>[
              for (final (label, onPressed) in actions)
                FilledButton.tonal(onPressed: onPressed, child: Text(label)),
            ],
          ),
          const SizedBox(height: 24),
          const _Spinner(),
        ],
      ),
    );
  }
}

/// Keeps frames flowing so FPS is visible.
class _Spinner extends StatefulWidget {
  const _Spinner();

  @override
  State<_Spinner> createState() => _SpinnerState();
}

class _SpinnerState extends State<_Spinner>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 2),
  )..repeat();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: RotationTransition(
        turns: _controller,
        child: const FlutterLogo(size: 72),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details')),
      body: Center(
        child: FilledButton(
          onPressed: () => throw FlutterError('Tapped the broken button'),
          child: const Text('Throw FlutterError'),
        ),
      ),
    );
  }
}
2
likes
160
points
502
downloads
screenshot

Documentation

API reference

Publisher

verified publisherwinterzxzz.tech

Weekly Downloads

In-app debug tool for Flutter on Android and iOS: FPS monitor, network inspector, logs, Firebase Analytics viewer and FCM push notification tester.

Repository (GitHub)
View/report issues

Topics

#debug #devtools #network #performance #analytics

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_debug_tracker

Packages that implement flutter_debug_tracker