Watchdog
A Flutter developer toolkit that streams HTTP requests, responses, BLoC lifecycle events, navigation, and app logs to a browser-based DevTools page in real-time. No IDE plugins, no native tooling, no account.
flutter run watchdog open
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ your app │◀────────│ browser tab │
│ Watchdog │ :8888 │ localhost │
└───────────────┘ └───────────────┘
2-minute quickstart
Five steps. Copy-paste each block as you go.
1. Add the dependency
pubspec.yaml:
dependencies:
watchdog: ^0.2.0
flutter pub get
2. Start before runApp
main.dart — pick the pattern that fits your project:
Option A — one-liner (recommended)
import 'package:watchdog/watchdog.dart';
// Replaces ensureInitialized + Watchdog.start + runApp in one call.
void main() => runWatchLocalApp(
const MyApp(),
stateManagement: StateManagement.bloc, // or .riverpod / .both / .none
);
runWatchLocalApp handles WidgetsFlutterBinding.ensureInitialized(),
Watchdog.start(), BLoC/Riverpod observer wiring, and runApp for you.
Option B — manual (more control)
import 'package:flutter/widgets.dart';
import 'package:watchdog/watchdog.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Watchdog.start(); // one call — initializes on first use
runApp(const MyApp());
}
Either way, Watchdog only runs in debug builds — release builds are a no-op, so it's safe to leave in production code.
3. Wire the observers where you already use your HTTP client / Navigator
Only add the lines you need. Skip the ones that don't apply.
// HTTP inspection — Chopper:
ChopperClient(interceptors: [Watchdog.chopperInterceptor]);
// HTTP inspection — Dio (same Network tab, identical view):
final dio = Dio()..interceptors.add(Watchdog.dioInterceptor);
// State management — Riverpod (Instances tab):
ProviderScope(observers: [Watchdog.providerObserver], child: const MyApp());
// Navigation events:
MaterialApp(navigatorObservers: [Watchdog.routeObserver]);
// DI registrations (GetIt) — after configureDependencies():
Watchdog.trackGetIt(getIt);
State management is optional and pluggable — use BLoC/Cubit, Riverpod,
or both. BLoC/Cubit lifecycle is wired automatically by Watchdog.start();
for Riverpod, add Watchdog.providerObserver to your ProviderScope. Both
feed the same Instances tab.
4. Install the CLI (once per machine)
dart pub global activate watchdog
Then add pub's bin directory to your PATH. Pick the block for your
shell (see PATH setup details if unsure):
macOS / Linux (zsh or bash)
echo 'export PATH="$PATH:$HOME/.pub-cache/bin"' >> ~/.zshrc
source ~/.zshrc
Use ~/.bashrc instead of ~/.zshrc if you're on bash.
Windows — Git Bash
echo 'watchdog() { "$(cygpath "$LOCALAPPDATA/Pub/Cache/bin/watchdog.bat")" "$@"; }' >> ~/.bashrc
source ~/.bashrc
Windows — PowerShell
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";$env:LOCALAPPDATA\Pub\Cache\bin", "User")
Close and reopen the terminal afterwards.
Verify:
watchdog version
5. Open DevTools
With the app running on a device or emulator:
watchdog open
Your browser opens http://localhost:8888. You're done.
What you get
- Network inspector — every Chopper or Dio HTTP request/response with headers, body, status, timing, and cURL export.
- BLoC lifecycle — creation, state changes, and closure of every Bloc and Cubit, with creation stack traces.
- Riverpod lifecycle — provider init, updates, and disposal in the same
Instances tab (optional; wire
Watchdog.providerObserver). - Navigation — push, pop, replace, remove events with a live route stack.
- Structured logging — debug / info / warning / error / critical levels.
- WebSocket tracker — send, receive, and connection state events.
- GetIt scanner — broadcasts every DI registration.
- Cloud streaming — optionally mirror events to a remote server.
- Replay buffer — late-connecting browsers receive full event history.
Daily usage
watchdog open # the one you'll use 99% of the time
watchdog open --port 9000 # custom port
watchdog open --no-adb # iOS-only / real-WiFi (skip adb)
watchdog forward # set up port forwarding, no browser
watchdog devices # list adb devices
watchdog version # print CLI version
Convenience logging
Watchdog.debug('Cache miss for key=$key');
Watchdog.info('Driver connected');
Watchdog.warning('GPS accuracy degraded: ${accuracy}m');
Watchdog.error('Sync failed', error: e, stackTrace: st);
WebSocket tracking
Watchdog.socketTracker.trackSend(channel: 'wss://api.example.com/ws', data: payload);
Watchdog.socketTracker.trackReceive(channel: 'wss://api.example.com/ws', data: decoded);
Configuration
Pass a WatchdogConfig to initialize() to override defaults:
await Watchdog.initialize(
config: const WatchdogConfig(
apiBaseUrl: 'https://api.example.com',
enableLogging: true,
),
);
| Parameter | Default | Description |
|---|---|---|
apiBaseUrl |
null |
Shown in the DevTools header for environment ID |
port |
8888 |
Local server port |
host |
0.0.0.0 |
Bind address |
enableLogging |
true |
Enable log events in the Logs tab |
maskSensitiveHeaders |
true |
Mask Authorization headers in the UI |
replayBufferSize |
10000 |
Max events retained for late clients |
enabled |
null |
null = kDebugMode; true forces enable |
global |
false |
When true, skips local server — streams to cloud only |
cloud |
null |
WatchdogCloudConfig for remote streaming |
device |
null |
WatchdogDevice — identifies this instance in the cloud dashboard |
Advanced
Custom logger — integrate your own logging backend
class MyLogger implements WatchdogLogger {
@override
void log(WatchdogLogLevel level, String message,
{String? title, Object? error, StackTrace? stackTrace}) {
// your implementation
}
}
await Watchdog.initialize(
dependencies: WatchdogDependencies(logger: MyLogger()),
);
Cloud streaming (local + cloud) — mirror events to a remote server alongside local DevTools
await Watchdog.initialize(
config: const WatchdogConfig(
cloud: WatchdogCloudConfig(
serverUrl: 'wss://watchdog-cloud.example.com',
apiKey: 'your-api-key',
appName: 'my-app',
),
),
);
Global (cloud-only) mode — stream remotely, skip localhost server
Use runWatchGlobalApp to disable the local server and stream only to the cloud.
Useful for observing devices that aren't physically connected to your machine.
import 'package:watchdog/watchdog.dart';
void main() => runWatchGlobalApp(
const MyApp(),
cloud: const WatchdogCloudConfig(
serverUrl: 'wss://watchdog-cloud.example.com',
apiKey: 'your-api-key',
appName: 'my-app',
),
device: WatchdogDevice(deviceName: 'Pixel 7', appVersion: '1.0.0'),
stateManagement: StateManagement.both,
);
Or manually with WatchdogConfig(global: true, cloud: ...).
WatchdogDevice — identify instances in the cloud dashboard
WatchdogDevice(
deviceName: 'Samsung Galaxy S24',
appVersion: '2.1.0',
// optional: platform, osVersion, etc. auto-detected when omitted
)
Pass this to runWatchGlobalApp(device: ...) or WatchdogConfig(device: ...).
Bridge an existing project logger
AppLogger.attachBridge(Watchdog.bridge);
Troubleshooting
watchdog: command not found
The pub cache bin directory isn't on your PATH. Redo step 4.
Browser shows "This site can't be reached" / ERR_CONNECTION_REFUSED Three things to check, in order:
- The Flutter app is running on a device/emulator (not just built).
- You're on a debug build (
flutter runwithout--release). adb deviceslists your device. Ifadbisn't on PATH, install Android platform-tools or usewatchdog open --no-adbfor iOS.
adb forward failed: cannot bind listener
A previous forwarding is stuck. Run:
adb forward --remove-all && watchdog open
PATH setup details
dart pub global activate installs the watchdog executable into the
Dart pub cache, but does not add that directory to your PATH. Until
you do it yourself, typing watchdog won't find the binary.
The pub cache lives at:
- macOS / Linux:
~/.pub-cache/bin - Windows:
%LOCALAPPDATA%\Pub\Cache\bin
On Windows + Git Bash, simply adding that directory to PATH isn't
enough because pub installs the executable as watchdog.bat and Git Bash
doesn't auto-append .bat when resolving commands. That's why step 4's
Git Bash block defines a shell function that invokes the .bat file
directly — it works regardless of extension handling.
Public API summary
Top-level helpers
| Function | Description |
|---|---|
runWatchLocalApp(app, ...) |
One-liner boot: local DevTools + optional BLoC/Riverpod wiring |
runWatchGlobalApp(app, cloud:, ...) |
One-liner boot: cloud-only mode (no localhost server) |
StateManagement enum values: none · bloc · riverpod · both
Watchdog class
| Accessor | Type | Description |
|---|---|---|
Watchdog.initialize() |
Future<void> |
One-time setup (optional — start() does it) |
Watchdog.start() |
Future<void> |
Initializes if needed + opens server + cloud |
Watchdog.stop() |
Future<void> |
Pauses capture |
Watchdog.dispose() |
Future<void> |
Full teardown |
Watchdog.logger |
WatchdogLogger |
Active logger instance |
Watchdog.bridge |
WatchdogLoggerBridge |
Legacy logger adapter |
Watchdog.blocObserver |
WatchdogBlocObserver |
BLoC/Cubit lifecycle observer |
Watchdog.providerObserver |
WatchdogProviderObserver |
Riverpod lifecycle observer |
Watchdog.routeObserver |
WatchdogRouteObserver |
Navigation observer |
Watchdog.chopperInterceptor |
WatchdogChopperInterceptor |
Chopper HTTP interceptor |
Watchdog.dioInterceptor |
WatchdogDioInterceptor |
Dio HTTP interceptor |
Watchdog.socketTracker |
WatchdogSocketTracker |
WebSocket tracker |
Watchdog.trackGetIt() |
void |
Scans GetIt container |
Watchdog.isInitialized |
bool |
Init state |
Watchdog.isRunning |
bool |
Server state |
License
MIT — see LICENSE.
Libraries
- watchdog
- Watchdog — Flutter developer toolkit.