api_track_inspector
Inspect every API call in minutes, not hours.
api_track_inspector adds a draggable in-app monitor with request details, timing, errors and export — and depends on nothing but the Flutter SDK.
dependencies:
api_track_inspector: ^2.0.0
Zero dependencies, on purpose
A debugging tool should never dictate your architecture. As of 2.0.0 this package has an empty dependencies: block — no GetX, no state-management library, no sizing library, no sharing plugin.
That means it:
- works the same under Bloc, Riverpod, Provider, GetX, signals or plain
setState; - needs no
navigatorKey, no service locator, noScreenUtilInitwrapper; - can never drag your transitive dependency versions around.
Upgrading from 1.x? See Migrating from 1.x — it is a small, mechanical change.
Screenshots
The carousel at the top of this package on pub.dev uses the same five images (from pubspec.yaml screenshots).
Preview (order: list → POST detail → bodies → error → 403 response):
Setup
1. Initialize
import 'package:flutter/foundation.dart';
import 'package:api_track_inspector/api_track_inspector.dart';
void main() {
NetworkInspector.init(enabled: kDebugMode);
runApp(const MyApp());
}
Keep it off in production. Logs hold full request and response bodies, including credentials, and the export button can copy them out of the app. Gate on
kDebugMode, a dev flavor, or both.
2. Mount the FAB
MaterialApp(
builder: (context, child) => NetworkInspector.wrapWithFAB(child!),
);
Works identically with MaterialApp.router, GoRouter, or any other routing setup. When enabled is false, wrapWithFAB returns child untouched, so it is safe to leave in release builds.
3. Log your traffic
Two calls: open a log on request, close it on response.
Dio
Copy this interceptor into your project (the package does not ship it — that would mean depending on Dio):
class InspectorInterceptor extends Interceptor {
static const _logIdKey = '_inspectorLogId';
static const _startKey = '_inspectorStart';
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final logId = NetworkInspector.logRequest(
method: options.method,
url: options.uri.toString(),
headers: Map<String, dynamic>.from(options.headers),
body: options.data,
);
if (logId != null) {
options.extra[_logIdKey] = logId;
options.extra[_startKey] = DateTime.now();
}
handler.next(options);
}
@override
void onResponse(Response<dynamic> res, ResponseInterceptorHandler handler) {
_complete(res.requestOptions, statusCode: res.statusCode, body: res.data);
handler.next(res);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
_complete(
err.requestOptions,
statusCode: err.response?.statusCode,
body: err.response?.data,
error: err.message ?? err.type.name,
);
handler.next(err);
}
void _complete(
RequestOptions options, {
int? statusCode,
dynamic body,
String? error,
}) {
final logId = options.extra[_logIdKey] as String?;
final start = options.extra[_startKey] as DateTime?;
if (logId == null) return;
NetworkInspector.logResponse(
logId: logId,
statusCode: statusCode,
body: body,
duration: start == null ? null : DateTime.now().difference(start),
error: error,
);
}
}
Register it last in your interceptor chain, so it observes headers your auth interceptor has already attached.
package:http (or anything else)
final logId = NetworkInspector.logRequest(method: 'GET', url: uri.toString());
final startedAt = DateTime.now();
final response = await http.get(uri);
if (logId != null) {
NetworkInspector.logResponse(
logId: logId,
statusCode: response.statusCode,
body: response.body,
duration: DateTime.now().difference(startedAt),
);
}
Opening the inspector
- The FAB — draggable, snaps to the nearest edge, tap to open. Tap the barrier, the close button, or the FAB again to dismiss.
- From your own code —
NetworkInspector.show(context)pushes it as a normal dialog route, so the system back button closes it.
IconButton(
icon: const Icon(Icons.bug_report),
onPressed: () => NetworkInspector.show(context),
);
context must be under a Navigator — any widget inside your screens qualifies. A context taken from MaterialApp.builder does not, because that builder runs above the Navigator; that is exactly why the FAB presents the inspector itself instead of pushing a route.
Sharing and export
By default the export and share buttons copy JSON to the clipboard, so the package needs no sharing plugin. To use a native share sheet, pass a handler:
NetworkInspector.init(
enabled: kDebugMode,
onShare: (data, subject) => SharePlus.instance.share(
ShareParams(text: data, subject: subject),
),
);
Configuration
| Option | Default | Purpose |
|---|---|---|
enabled |
true |
Master switch. False makes every API a no-op. |
maxLogs |
100 |
Ring-buffer size; oldest entries are dropped. |
primaryColor / secondaryColor |
Blue | FAB and header gradient. |
fabBottomPosition / fabRightPosition |
100 / 16 |
Starting FAB position. |
showShareButton |
true |
Per-log share button. |
onShare |
null |
Share handler; falls back to the clipboard. |
API
| Member | Purpose |
|---|---|
NetworkInspector.init(...) |
Configure and start. |
NetworkInspector.wrapWithFAB(child) |
Mount the draggable FAB. |
NetworkInspector.show(context) |
Open as a dialog route. |
NetworkInspector.logRequest(...) |
Open a log; returns its id, or null when disabled. |
NetworkInspector.logResponse(...) |
Close a log by id. |
NetworkInspector.clearLogs() |
Drop all logs. |
NetworkInspector.exportLogs() |
All logs as a JSON string. |
NetworkInspector.service |
The NetworkInspectorService (a ChangeNotifier). |
NetworkInspector.maybeService |
Same, but null instead of throwing. |
NetworkInspector.reset() |
Tear down and drop logs (useful in tests). |
NetworkInspectorService is a plain ChangeNotifier, so you can build your own UI on it:
ListenableBuilder(
listenable: NetworkInspector.service,
builder: (context, _) => Text('${NetworkInspector.service.logs.length}'),
);
Migrating from 1.x
2.0.0 removes GetX. Everything below is mechanical:
| 1.x | 2.0.0 |
|---|---|
NetworkInspector.showDialog() |
NetworkInspector.show(context) |
NetworkInspector.onRequest(request) (GetConnect) |
Removed — use logRequest / logResponse |
NetworkInspector.onResponse(req, res) (GetConnect) |
Removed — as above |
NetworkInspector.setRequestBody(body) |
Removed — pass body: to logRequest |
Share used share_plus automatically |
Pass onShare:, or accept the clipboard fallback |
Host app needed ScreenUtilInit |
No longer required |
NetworkInspectorService extends GetxService |
extends ChangeNotifier |
GetConnect users: the two convenience wrappers are gone because they took GetX types in their signature. Call logRequest / logResponse from your own request and response modifiers instead — same two calls as every other client.
If you still want the FAB and the GetX-based dialog, pin api_track_inspector: 1.1.18.
Requirements
- Flutter
>=3.27.0, Dart>=3.6.0.
License
MIT
Libraries
- api_track_inspector
- Network Inspector — a dependency-free API debugging tool for Flutter.