api_track_inspector 2.0.0 copy "api_track_inspector: ^2.0.0" to clipboard
api_track_inspector: ^2.0.0 copied to clipboard

Dependency-free Flutter network inspector for debug builds. Logs any HTTP client (Dio, http, chopper) and shows requests in an in-app viewer behind a draggable FAB.

example/lib/main.dart

import 'package:api_track_inspector/api_track_inspector.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:share_plus/share_plus.dart';

export 'package:dio/dio.dart' show RequestOptions, Response;
export 'package:dio/dio.dart'
    show RequestInterceptorHandler, ResponseInterceptorHandler;

void main() {
  NetworkInspector.init(
    enabled: kDebugMode,
    maxLogs: 100,
    // Optional. Without this the export/share buttons copy to the clipboard,
    // so the package itself needs no sharing plugin.
    onShare: (data, subject) => SharePlus.instance.share(
      ShareParams(text: data, subject: subject),
    ),
  );

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Network Inspector Example',
      theme: ThemeData(useMaterial3: true),
      home: const HomePage(),
      // wrapWithFAB returns child untouched when the inspector is disabled,
      // so this is safe to leave in for release builds.
      builder: (context, child) => NetworkInspector.wrapWithFAB(
        child ?? const SizedBox.shrink(),
      ),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  final _dio = Dio();
  String _result = 'Run any request and open the inspector FAB.';

  @override
  void initState() {
    super.initState();
    _dio.interceptors.add(InspectorInterceptor());
  }

  Future<void> _sendWithHttp() async {
    final uri = Uri.parse('https://jsonplaceholder.typicode.com/todos/1');
    final startedAt = DateTime.now();

    final logId = NetworkInspector.logRequest(
      method: 'GET',
      url: uri.toString(),
      headers: const {'Accept': 'application/json'},
    );

    try {
      final response = await http.get(uri);
      final duration = DateTime.now().difference(startedAt);

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

      setState(() {
        _result = 'package:http -> ${response.statusCode} '
            '(${duration.inMilliseconds}ms)';
      });
    } catch (e) {
      if (logId != null) {
        NetworkInspector.logResponse(
          logId: logId,
          duration: DateTime.now().difference(startedAt),
          error: e.toString(),
        );
      }
      setState(() => _result = 'package:http failed: $e');
    }
  }

  Future<void> _sendWithDio() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/posts/1',
      );
      setState(() => _result = 'Dio -> ${response.statusCode}');
    } catch (e) {
      setState(() => _result = 'Dio failed: $e');
    }
  }

  Future<void> _sendFailingRequest() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/nope/404',
      );
      setState(() => _result = 'Dio -> ${response.statusCode}');
    } catch (e) {
      setState(() => _result = 'Dio failed as expected (see the inspector)');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Network Inspector Example')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ElevatedButton(
              onPressed: _sendWithDio,
              child: const Text('Send with Dio'),
            ),
            const SizedBox(height: 12),
            ElevatedButton(
              onPressed: _sendWithHttp,
              child: const Text('Send with package:http'),
            ),
            const SizedBox(height: 12),
            ElevatedButton(
              onPressed: _sendFailingRequest,
              child: const Text('Send a failing request'),
            ),
            const SizedBox(height: 12),
            // The FAB is always available, but you can open the inspector
            // from anywhere with a BuildContext.
            OutlinedButton(
              onPressed: () => NetworkInspector.show(context),
              child: const Text('Open inspector'),
            ),
            const SizedBox(height: 24),
            Text(_result),
          ],
        ),
      ),
    );
  }
}

/// A ready-to-copy Dio interceptor.
///
/// The package does not ship this — that would mean depending on Dio. Copy it
/// into your own project (it is ~30 lines) and adapt as needed.
class InspectorInterceptor extends Interceptor {
  static const String _logIdKey = '_inspectorLogId';
  static const String _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> response,
    ResponseInterceptorHandler handler,
  ) {
    _complete(
      response.requestOptions,
      statusCode: response.statusCode,
      body: response.data,
    );
    handler.next(response);
  }

  @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,
    );
  }
}
1
likes
160
points
137
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Dependency-free Flutter network inspector for debug builds. Logs any HTTP client (Dio, http, chopper) and shows requests in an in-app viewer behind a draggable FAB.

Repository (GitHub)
View/report issues

Topics

#networking #debugging #http #dio #devtools

License

MIT (license)

Dependencies

flutter

More

Packages that depend on api_track_inspector