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 sharing, with quick integration for GetConnect, Dio, and package:http.

No setup overhead:

  • Initialize once.
  • Wrap your app with NetworkInspector.wrapWithFAB.
  • Plug in your client interceptors (onRequest, onResponse) or manual hooks (logRequest, logResponse).

pub package License: MIT

Screenshots

The carousel at the top of this package on pub.dev uses the same five images (from pubspec.yaml screenshots).

README previews below use absolute GitHub raw URLs so they render on pub.dev (relative doc/... paths in <img> tags are not rewritten there, which is why alt text was showing instead of pictures).

Preview (order: list → POST detail → bodies → error → 403 response):

Request list POST request detail Request and response bodies

Error detail 403 response and headers

Quick AI prompt

Copy/paste this into your AI assistant:

Integrate `api_track_inspector` into my Flutter app with dev-only visibility.
Requirements:
1) Initialize with NetworkInspector.init(enabled: AppConfig.isDev) in startup.
2) Optionally configure maxLogs, colors, FAB position, and showShareButton.
3) Wrap app root using NetworkInspector.wrapWithFAB(child) in MaterialApp/GetMaterialApp builder.
4) If project uses GetConnect, add NetworkInspector.onRequest/onResponse interceptors.
5) If project uses Dio or package:http, wire NetworkInspector.logRequest/logResponse manually.
6) Keep release builds free from inspector UI/overhead.

Why use it

  • Capture request/response data while testing APIs.
  • Keep debugging UI inside the app (no external proxy needed).
  • Integrate quickly with GetConnect (onRequest, onResponse) or manually via logRequest, logResponse.

Installation

1) Install from pub.dev (primary)

dependencies:
  api_track_inspector: ^1.1.18

2) Install from Git (secondary)

dependencies:
  api_track_inspector:
    git:
      url: https://github.com/abduwael/network_inspector.git
      ref: master

Then run:

flutter pub get

Quick start

Initialize once

import 'package:flutter/foundation.dart';
import 'package:api_track_inspector/api_track_inspector.dart';

void main() {
  const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
  final isDevFlavor = flavor == 'dev';

  NetworkInspector.init(
    enabled: kDebugMode && isDevFlavor, // visible only in debug + dev flavor
    maxLogs: 100,
  );
  runApp(const MyApp());
}

Initialize with your flavor flag (dev only)

If your app already has a config like AppConfig.isDev, use:

import 'package:api_track_inspector/api_track_inspector.dart';

void main() {
  NetworkInspector.init(
    enabled: AppConfig.isDev,
  );
  runApp(const MyApp());
}

With optional configuration features:

NetworkInspector.init(
  enabled: AppConfig.isDev,
  maxLogs: 100,
  primaryColor: const Color(0xFF2196F3),
  secondaryColor: const Color(0xFF1565C0),
  fabBottomPosition: 100,
  fabRightPosition: 16,
  showShareButton: true,
);

Real app pattern (MaterialApp + app services)

This is a production-friendly pattern using regular MaterialApp with flavor-safe behavior:

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

import 'app/core/config/app_config.dart';

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) {
        // Show inspector FAB only in dev flavor.
        if (AppConfig.isDev && child != null) {
          child = NetworkInspector.wrapWithFAB(child);
        }

        return child ?? const SizedBox.shrink();
      },
    );
  }
}

Future<void> initServices() async {
  // ... your DI/services

  NetworkInspector.init(
    enabled: AppConfig.isDev, // dev flavor only
  );
}

Add floating inspector button

MaterialApp(
  builder: (context, child) {
    if (child == null) return const SizedBox.shrink();
    return NetworkInspector.wrapWithFAB(child);
  },
);

Integration guides

GetConnect interceptor usage

onRequest reads a temporary body value, so set it before body-based calls.

import 'package:get/get.dart';
import 'package:api_track_inspector/api_track_inspector.dart';

class ApiProvider extends GetConnect {
  @override
  void onInit() {
    httpClient.addRequestModifier<dynamic>((request) {
      return NetworkInspector.onRequest(request);
    });

    httpClient.addResponseModifier<dynamic>((request, response) {
      return NetworkInspector.onResponse(request, response);
    });

    super.onInit();
  }

  Future<Response<T>> postWithInspector<T>(String url, dynamic body) {
    NetworkInspector.setRequestBody(body);
    return post<T>(url, body);
  }

  Future<Response<T>> putWithInspector<T>(String url, dynamic body) {
    NetworkInspector.setRequestBody(body);
    return put<T>(url, body);
  }
}

Dio interceptor usage

import 'package:dio/dio.dart';
import 'package:api_track_inspector/api_track_inspector.dart';

final dio = Dio();

dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      final logId = NetworkInspector.logRequest(
        method: options.method,
        url: options.uri.toString(),
        headers: Map<String, dynamic>.from(options.headers),
        body: options.data,
      );
      options.extra['_inspectorLogId'] = logId;
      options.extra['_inspectorStart'] = DateTime.now();
      handler.next(options);
    },
    onResponse: (response, handler) {
      final logId = response.requestOptions.extra['_inspectorLogId'] as String?;
      final start = response.requestOptions.extra['_inspectorStart'] as DateTime?;
      if (logId != null && start != null) {
        NetworkInspector.logResponse(
          logId: logId,
          statusCode: response.statusCode,
          body: response.data,
          duration: DateTime.now().difference(start),
        );
      }
      handler.next(response);
    },
    onError: (error, handler) {
      final logId = error.requestOptions.extra['_inspectorLogId'] as String?;
      final start = error.requestOptions.extra['_inspectorStart'] as DateTime?;
      if (logId != null && start != null) {
        NetworkInspector.logResponse(
          logId: logId,
          statusCode: error.response?.statusCode,
          body: error.response?.data,
          duration: DateTime.now().difference(start),
          error: error.message,
        );
      }
      handler.next(error);
    },
  ),
);

package:http wrapped client usage

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:api_track_inspector/api_track_inspector.dart';

class InspectorHttpClient extends http.BaseClient {
  final http.Client _inner;
  InspectorHttpClient(this._inner);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    final startedAt = DateTime.now();

    final logId = NetworkInspector.logRequest(
      method: request.method,
      url: request.url.toString(),
      headers: Map<String, dynamic>.from(request.headers),
    );

    try {
      final streamed = await _inner.send(request);
      final duration = DateTime.now().difference(startedAt);

      final bodyBytes = await streamed.stream.toBytes();
      final bodyText = utf8.decode(bodyBytes, allowMalformed: true);

      if (logId != null) {
        NetworkInspector.logResponse(
          logId: logId,
          statusCode: streamed.statusCode,
          body: bodyText,
          duration: duration,
        );
      }

      return http.StreamedResponse(
        Stream.fromIterable([bodyBytes]),
        streamed.statusCode,
        request: streamed.request,
        headers: streamed.headers,
        reasonPhrase: streamed.reasonPhrase,
        isRedirect: streamed.isRedirect,
        persistentConnection: streamed.persistentConnection,
        contentLength: streamed.contentLength,
      );
    } catch (e) {
      if (logId != null) {
        NetworkInspector.logResponse(
          logId: logId,
          duration: DateTime.now().difference(startedAt),
          error: e.toString(),
        );
      }
      rethrow;
    }
  }
}

API reference

  • init(...): initialize the inspector and config.
  • wrapWithFAB(child): overlay draggable FAB to open dialog.
  • onRequest(request): GetConnect request hook.
  • onResponse(request, response): GetConnect response hook.
  • logRequest(...): manual request logging (Dio/http/others).
  • logResponse(...): manual response/error logging.
  • clearLogs(): clear in-memory logs.
  • exportLogs(): export logs JSON string.

AI quick prompt (copy/paste)

Use this in your AI coding assistant:

Integrate `api_track_inspector` into my Flutter app with debug + flavor-safe visibility.
Requirements:
1) Call NetworkInspector.init in main() with:
   - enabled: kDebugMode && (current flavor == dev)
   - so inspector appears in dev builds and never in production.
2) Wrap app root using NetworkInspector.wrapWithFAB(child) in MaterialApp/GetMaterialApp builder.
3) If project uses GetConnect, add NetworkInspector.onRequest/onResponse interceptors.
4) If project uses Dio or package:http, wire NetworkInspector.logRequest/logResponse manually.
5) Keep the integration production-safe with zero release visibility/overhead.

Notes

  • Recommended for development and QA builds.
  • For production apps, keep inspector disabled (for example: enabled: kDebugMode && flavor == 'dev').

License

MIT License. See LICENSE.

Libraries

api_track_inspector
Network Inspector - A professional API debugging tool for Flutter