flutter_debug_tracker: in-app debug tool for Flutter (Android & iOS)

pub package platform license: MIT GitHub stars

flutter_debug_tracker: in-app debug overlay for Flutter with FPS monitor, network inspector and Firebase Analytics viewer

flutter_debug_tracker is an in-app debugger for Flutter. A floating debug bubble sits over your app and opens a full debug panel, on the phone, without DevTools or a cable. It combines a performance monitor (FPS, jank, frame time, memory), a network inspector for HTTP requests, a logger with crash capture, a Firebase Analytics event viewer, a push notification simulator for FCM, route history, device info, a file browser and UI debugging tools.

It is inspired by DebugSwift for iOS and works on Android and iOS, with no third-party dependencies (a small native layer only shows test notifications). It is off by default in release builds.

Screenshots

Debug bubble + HUD Performance monitor Network inspector
Floating Flutter debug bubble with live FPS and performance HUD Flutter FPS, jank, frame time and memory monitor Flutter network inspector listing HTTP requests
Request details Firebase Analytics events Logs and crashes
HTTP response headers and JSON body in the Flutter network inspector Firebase Analytics logEvent calls captured in a Flutter app Flutter in-app console with print, debugPrint and uncaught errors
Send a test push Test notification + history UI tools
Flutter push notification simulator with templates, data payload and delay Test push notification shown on Android with delivered and tapped status Flutter UI debugging tools: grid, touches, slow animations, layout bounds

Features

Performance monitor: FPS, jank and memory

  • Live FPS in the bubble, color-coded against the display refresh rate.
  • UI (build) and raster time per frame, a frame-time chart with the frame budget, the jank count and rate, and the worst frame.
  • Memory (RSS) and peak memory with a 2-minute history chart.
  • A compact on-screen performance HUD, toggled by long-pressing the bubble.
  • Custom traces that time your own work, such as API calls, database loads or screen builds.

Network inspector: HTTP request logger

  • Captures every dart:io HttpClient request. This covers package:http and Dio's default adapter, with no interceptor to add.
  • Shows the method, URL, status, duration and size, request and response headers, pretty-printed JSON bodies, and errors.
  • Search, an errors-only filter, and copy as cURL.

Logs and crash reporting

  • Captures print, debugPrint, DebugTracker.log, FlutterError and uncaught async errors, with stack traces.
  • Filter by level and search. The bubble shows an error badge.

Firebase Analytics event viewer

  • Every FirebaseAnalytics.instance.logEvent (and logScreenView, logPurchase, ...), plus setUserId, setUserProperty, consent and default parameters, captured automatically.
  • Events from other analytics SDKs (Mixpanel, Amplitude, your own backend) via DebugTracker.logAnalyticsEvent.

Push notification simulator (FCM)

  • Send a real system notification from the debug panel to test push handling, like DebugSwift's push simulator: templates, title, body, a data payload, and a delay (now, 5, 10 or 30 s) so you can put the app in the background first.
  • Optionally deliver it to firebase_messaging: FirebaseMessaging.onMessage when it appears in the foreground and onMessageOpenedApp when tapped. Your real FCM handlers run, with no Firebase console or server needed.
  • Status for each test notification (scheduled, delivered, tapped), plus a log of real FCM traffic: the token (register()/getToken()), foreground messages, notification taps, the initial message, and topic subscriptions.
  • An advanced mode delivers a raw FCM JSON payload (paste it or open a .json file from the Files tab).

More tools

  • Routes: the current navigator stack and push/pop history.
  • App and device info: OS version, locale, CPU cores, build mode, screen size, pixel ratio, refresh rate, safe area, plus your own fields (flavor, user id, ...).
  • File browser: read-only view of the app sandbox (Documents, Library and tmp on iOS; files, cache, shared_prefs and databases on Android).
  • UI tools: Flutter's performance overlay, an 8 pt grid, touch indicators, slow animations, layout bounds and repaint rainbow.

Installation

flutter pub add flutter_debug_tracker

Requires Flutter 3.38 or newer.

Quick start

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

void main() {
  // Starts every tracker and captures print() inside the zone.
  // Do not call WidgetsFlutterBinding.ensureInitialized() before this;
  // call it inside the callback if you need it.
  DebugTracker.runZoned(() => runApp(const MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [DebugTracker.observer],
      builder: DebugTracker.builder(),
      home: const HomePage(),
    );
  }
}

Run the app and tap the bubble.

Gesture Action
Tap the bubble Open the debug panel
Long-press the bubble Toggle the performance HUD
Drag the bubble Move it; it snaps to the nearest edge
System back or back swipe Close a panel page, then the panel

Already using MaterialApp.builder? Pass it through:

builder: DebugTracker.builder((context, child) => MyWrapper(child: child!)),

CupertinoApp and WidgetsApp work the same way.

Logs and custom traces

DebugTracker.log('User signed in', tag: 'auth');
DebugTracker.log('Token expires soon', level: LogLevel.warning);

final feed = await DebugTracker.trace('load feed', () => api.loadFeed());

Programmatic control

DebugTracker.open();                               // open the panel
DebugTracker.close();                              // close it
DebugTracker.instance.bubbleVisible.value = false; // hide the bubble
DebugTracker.instance.hudVisible.value = true;     // show the HUD
DebugTracker.stop();                               // remove every hook

Firebase Analytics debugging

You don't need DebugView or adb shell setprop debug.firebase.analytics.app. Start the app with DebugTracker.runZoned and keep calling Firebase as usual:

void main() {
  DebugTracker.runZoned(() async {
    WidgetsFlutterBinding.ensureInitialized(); // fine inside runZoned
    await Firebase.initializeApp();
    runApp(const MyApp());
  });
}

// Anywhere in the app: shows up in the Analytics tab right away.
await FirebaseAnalytics.instance.logEvent(
  name: 'add_to_cart',
  parameters: {'item_id': 'sku-42', 'price': 19.99},
);

How it works: the tracker installs a DebugTrackerBinding whose platform messenger reads the messages firebase_analytics sends to the native Firebase SDK. You therefore see exactly what Firebase receives, and this package does not depend on firebase_analytics.

For other providers:

DebugTracker.logAnalyticsEvent(
  'checkout_started',
  provider: 'mixpanel',
  parameters: {'cart_value': 42.5},
);

Push notification testing (FCM)

Open the bubble, go to Push, then Send test notification. You can also send one from code:

await DebugTracker.sendTestNotification(
  title: 'Order shipped',
  body: 'Your order #1042 is on its way',
  data: {'orderId': '1042'},
  delay: const Duration(seconds: 5), // time to background the app
);

If the app uses FCM, keep your handlers as they are:

FirebaseMessaging.onMessage.listen((message) {
  // Runs for a test notification shown while the app is open.
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
  // Runs when the user taps the test notification.
});

Without FCM, listen to the taps directly:

DebugTracker.instance.notifications.onTap.listen((n) {
  router.go(n.data['route'] ?? '/');
});

Notes:

  • Android 13+ asks for the notification permission on the first send. The package declares POST_NOTIFICATIONS. If your app has no notifications and you don't want the permission in release builds, remove it in android/app/src/release/AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <uses-permission android:name="android.permission.POST_NOTIFICATIONS"
            tools:node="remove" />
    </manifest>
    
  • Nothing native changes until you send the first test notification. On iOS the plugin then answers only for its own notifications; every other notification still goes to your app's or Firebase's delegate.

  • Delivery to firebase_messaging needs the app to start with DebugTracker.runZoned and to have used FirebaseMessaging.instance.

  • iOS: a tap that launches a killed app is reported only when the app's AppDelegate sets UNUserNotificationCenter.current().delegate = self (the usual setup for local notifications). Taps while the app is running always work.

Configuration

DebugTracker.runZoned(
  () => runApp(const MyApp()),
  config: DebugTrackerConfig(
    enabled: !kReleaseMode,          // default: off in release builds
    trackPerformance: true,
    trackNetwork: true,
    trackLogs: true,
    trackErrors: true,
    trackAnalytics: true,
    trackPush: true,
    showPerformanceHud: false,
    maxNetworkEntries: 200,
    maxLogEntries: 1000,
    maxAnalyticsEvents: 500,
    maxBodyBytes: 64 * 1024,
    networkIgnore: (uri) => uri.host.contains('crashlytics'),
    customInfo: {'Flavor': 'staging', 'User': '42'},
  ),
);

To ship the tracker in an internal QA build, pass enabled: true behind your own flag.

Performance overhead

A debug tool should not slow down the app it measures:

  • Frame data comes from SchedulerBinding.addTimingsCallback, which the engine already batches. No per-frame callback or ticker is added.
  • Metrics and memory are sampled once per second.
  • Captured data lives in fixed-size ring buffers, and request/response bodies are capped, so memory use does not grow with uptime. Binary bodies (images, video, fonts) are never stored.
  • List updates are coalesced, and panel widgets are only built while the panel is open.
  • The bubble only repaints when the shown FPS changes, so an idle app stays idle.
  • With enabled: false (the release default), no Dart hook is installed. The small native notification plugin stays idle unless a test notification is sent.

FPS counts frames Flutter actually rendered in the last second. Flutter only renders when something changes, so an idle screen shows 0 FPS. For real numbers, run in profile mode (flutter run --profile).

FAQ

How do I inspect network requests in a Flutter app on a real device? Add DebugTracker.builder() and start with DebugTracker.runZoned. Every HttpClient, http and Dio request then appears in the Network tab with headers, body and timing. You don't need a proxy or a certificate.

How can I see Firebase Analytics events while developing? Open the Analytics tab. Each logEvent, user property and user id call appears immediately with its parameters. There is no DebugView delay.

How do I measure FPS and jank in Flutter? The bubble shows live FPS. The Performance tab shows UI and raster time per frame, the jank rate and memory. Long-press the bubble for an always-on HUD.

How do I test push notifications in Flutter without sending a real FCM message? Use Push > Send test notification. It shows a real notification and, optionally, hands it to FirebaseMessaging.onMessage and onMessageOpenedApp, so your handlers and deep links run as for a real push.

Does it affect my release build? No. It is disabled in release mode by default, and the overlay then renders only your app.

Does it work with Dio, GetX, Riverpod or BLoC? Yes. Network capture works below Dio's default adapter, and the overlay is a plain widget, so it works with any state management.

Limitations

  • Network capture covers dart:io HttpClient. Clients that bypass it (cupertino_http, cronet_http, native SDKs, WebViews) are not captured. If another library sets HttpOverrides.global after the tracker starts, wrap its clients with DebugTracker.instance.network.wrap(client).
  • print, Firebase Analytics and Firebase Messaging are captured automatically only when the app starts with DebugTracker.runZoned.
  • FCM messages handled in the background isolate (FirebaseMessaging.onBackgroundMessage) are not captured. Test notifications are delivered to the foreground handlers only.
  • Analytics events logged by native code (not through Dart) are not captured.
  • "Hide bubble" in the Tools tab hides it until you set DebugTracker.instance.bubbleVisible.value = true or restart the app.
  • Supported platforms: Android and iOS.

Example

See example/ for a demo app that generates network traffic, logs, errors, jank, traces, analytics events and test notifications.

License

MIT. See LICENSE.

Libraries

flutter_debug_tracker
In-app debug overlay for Flutter apps on Android and iOS.