ispectify_dio 4.8.0-dev01 copy "ispectify_dio: ^4.8.0-dev01" to clipboard
ispectify_dio: ^4.8.0-dev01 copied to clipboard

Dio HTTP client integration for ISpect toolkit

ISpect is a production-safe debugging toolkit for Flutter. It provides visual inspection, structured logging, network monitoring, and data redaction — all automatically removed from release builds via compile-time tree-shaking.

Live Web Demo — drag and drop exported log files to explore them in the browser.


Why ISpect? #

Most Flutter debugging tools stay in your binary. ISpect doesn't — when ISPECT_ENABLED is not defined, the entire toolkit compiles to no-ops and is eliminated by Dart's tree-shaker. Zero bytes in production.

Capability What it does
Zero-footprint builds Compile-time const guard removes all code from release APK/IPA
Visual inspector Tap any widget to see its render box, padding, constraints, and color
Structured logs Typed log entries with levels, filtering, export/import, and session history
Network capture Request/response inspection for Dio, http, and WebSocket clients
Automatic redaction Tokens, passwords, PII, and credit cards masked before they reach logs
Observer hooks Forward log events to Sentry, Crashlytics, or any backend in real-time
12 languages en, ru, kk, zh, es, fr, de, pt, ar, ko, ja, hi

Quick Start #

dependencies:
  ispect: ^4.8.0-dev01
import 'package:flutter/material.dart';
import 'package:ispect/ispect.dart';

void main() {
  ISpect.run(() => runApp(const MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: ISpectLocalizations.delegates(),
      navigatorObservers: ISpectNavigatorObserver.observers(),
      builder: (_, child) => ISpectBuilder.wrap(child: child!),
      home: const HomePage(),
    );
  }
}
# Development — toolkit active
flutter run --dart-define=ISPECT_ENABLED=true

# Release — toolkit removed via tree-shaking
flutter build apk

When ISPECT_ENABLED is not set (default), ISpect.run(), ISpectBuilder, and ISpectLocalizations.delegates() become no-ops. Dart's tree-shaker strips everything out.


Production Safety #

Debug tools can expose API keys, tokens, and user data. ISpect solves this at the compiler level.

flutter build apk --release --obfuscate --split-debug-info=debug-info/
Build APK Size "ispect" strings
Obfuscated release 42.4 MB 6
Non-obfuscated release 44.5 MB 34
Development 51.0 MB 276

For environment-based control:

import 'package:flutter/foundation.dart';

class ISpectConfig {
  static const bool isEnabled = bool.fromEnvironment(
    'ISPECT_ENABLED',
    defaultValue: kDebugMode,
  );

  static const String environment = String.fromEnvironment(
    'ENVIRONMENT',
    defaultValue: 'development',
  );

  static bool get shouldInitialize => isEnabled && environment != 'production';
}

void main() {
  if (ISpectConfig.shouldInitialize) {
    ISpect.run(() => runApp(const MyApp()));
  } else {
    runApp(const MyApp());
  }
}
flutter build apk \
  --dart-define=ISPECT_ENABLED=true \
  --dart-define=ENVIRONMENT=staging

Logger Configuration #

void main() {
  final logger = ISpectFlutter.init(
    options: ISpectLoggerOptions(
      enabled: true,
      useHistory: true,
      useConsoleLogs: kDebugMode,
      maxHistoryItems: 5000,
      logTruncateLength: 4000,
    ),
  );

  ISpect.run(logger: logger, () => runApp(const MyApp()));
}

Disable console output (logs still flow to observers and UI):

ISpect.logger.configure(
  options: ISpect.logger.options.copyWith(useConsoleLogs: false),
);

Streaming-only (no in-memory history, useful for observer-driven pipelines):

final logger = ISpectFlutter.init(
  options: const ISpectLoggerOptions(useHistory: false),
);

Filter by priority:

class WarningsAndAbove implements ISpectFilter {
  @override
  bool apply(ISpectLogData data) {
    return (data.logLevel?.priority ?? 0) >= LogLevel.warning.priority;
  }
}

final logger = ISpectFlutter.init(filter: WarningsAndAbove());

Localization #

ISpect ships with 12 built-in locales. Pass delegates through ISpectLocalizations.delegates() — it merges ISpect's translations with your app's own delegates in a single call:

MaterialApp(
  localizationsDelegates: ISpectLocalizations.delegates(
    delegates: [
      // your app's delegates go here
    ],
  ),
)

To force a specific locale for the debug panel regardless of the app locale:

ISpectBuilder(
  options: ISpectOptions(
    observer: observer,
    locale: const Locale('ru'),
  ),
  child: child ?? const SizedBox.shrink(),
)

Observers #

Observers tap into the log stream without coupling your app to ISpect's internals. Use them to bridge events to any external service.

class SentryISpectObserver implements ISpectObserver {
  @override
  void onLog(ISpectLogData data) {
    // Add as Sentry breadcrumb
  }

  @override
  void onError(ISpectLogData err) {
    // Sentry.captureException(err.exception, stackTrace: err.stackTrace);
  }

  @override
  void onException(ISpectLogData err) {
    // Sentry.captureException(err.exception, stackTrace: err.stackTrace);
  }
}

void main() {
  final logger = ISpectFlutter.init();
  logger.addObserver(SentryISpectObserver());

  ISpect.run(logger: logger, () => runApp(const MyApp()));
}

Data Redaction #

Sensitive data is automatically masked before it reaches logs or observers. Built-in patterns cover auth tokens, passwords, API keys, PII (SSN, passport), credit cards, and bank accounts.

Note: Network interceptors (ISpectDioInterceptor, ISpectHttpInterceptor, ISpectWSInterceptor) have enableRedaction: true by default. Authorization headers, tokens, passwords, and other sensitive data are automatically redacted. If you need to see unredacted data during debugging, you can opt out:

ISpectDioInterceptor(
  settings: const ISpectDioInterceptorSettings(enableRedaction: false),
);

The built-in SettingsBuilder.production() and SettingsBuilder.staging() factory constructors also enable redaction automatically.

// Extend redaction with your own keys
final redactor = RedactionService();
redactor.ignoreKeys(['authorization', 'x-api-key']);
redactor.ignoreValues(['<test-token>']);

// Database-level redaction
ISpectDbCore.config = const ISpectDbConfig(
  redact: true,
  redactKeys: ['password', 'token', 'secret'],
);

// Disable for non-sensitive test data
ISpectDioInterceptor(
  settings: const ISpectDioInterceptorSettings(enableRedaction: false),
);

Modular Packages #

Install only what your project needs. Each package works independently.

dependencies:
  ispect: ^4.8.0-dev01 # Core UI, inspector, log viewer
  ispectify: ^4.8.0-dev01 # Logging backbone (Dart-only, no Flutter)
  ispectify_dio: ^4.8.0-dev01 # Dio HTTP interceptor
  ispectify_http: ^4.8.0-dev01 # http package interceptor
  ispectify_ws: ^4.8.0-dev01 # WebSocket traffic capture
  ispectify_db: ^4.8.0-dev01 # Database operation tracking
  ispectify_bloc: ^4.8.0-dev01 # BLoC event/state observer

Dio #

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

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));

ISpect.run(
  () => runApp(MyApp()),
  logger: logger,
  onInit: () {
    dio.interceptors.add(
      ISpectDioInterceptor(
        logger: logger,
        settings: const ISpectDioInterceptorSettings(
          printRequestHeaders: true,
          printResponseHeaders: true,
          printRequestData: true,
          printResponseData: true,
        ),
      ),
    );
  },
);

http #

import 'package:http_interceptor/http_interceptor.dart' as http_interceptor;
import 'package:ispectify_http/ispectify_http.dart';

final client = http_interceptor.InterceptedClient.build(interceptors: []);

ISpect.run(
  () => runApp(MyApp()),
  logger: logger,
  onInit: () {
    client.interceptors.add(
      ISpectHttpInterceptor(
        logger: logger,
        settings: const ISpectHttpInterceptorSettings(
          printRequestHeaders: true,
          printResponseHeaders: true,
        ),
      ),
    );
  },
);

WebSocket #

import 'package:ws/ws.dart';
import 'package:ispectify_ws/ispectify_ws.dart';

final interceptor = ISpectWSInterceptor(
  logger: logger,
  settings: const ISpectWSInterceptorSettings(
    enabled: true,
    printSentData: true,
    printReceivedData: true,
    printReceivedMessage: true,
    printErrorData: true,
    printErrorMessage: true,
  ),
);

final client = WebSocketClient(
  WebSocketOptions.common(interceptors: [interceptor]),
);

interceptor.setClient(client);

Database #

import 'package:sqflite/sqflite.dart';
import 'package:ispectify_db/ispectify_db.dart';

ISpectDbCore.config = const ISpectDbConfig(
  sampleRate: 1.0,
  redact: true,
  attachStackOnError: true,
  slowQueryThreshold: Duration(milliseconds: 400),
);

final rows = await ISpect.logger.dbTrace<List<Map<String, Object?>>>(
  source: 'sqflite',
  operation: 'query',
  statement: 'SELECT * FROM users WHERE id = ?',
  args: [userId],
  table: 'users',
  run: () => db.rawQuery('SELECT * FROM users WHERE id = ?', [userId]),
  projectResult: (rows) => {'rows': rows.length},
);

BLoC #

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ispectify_bloc/ispectify_bloc.dart';

ISpect.run(
  () => runApp(MyApp()),
  logger: logger,
  onInit: () {
    Bloc.observer = ISpectBlocObserver(logger: logger);
  },
);

Theming and Customization #

ISpectBuilder(
  options: ISpectOptions(observer: observer),
  theme: ISpectTheme(
    pageTitle: 'Debug Panel',
    background: ISpectDynamicColor(light: Colors.white, dark: Colors.black),
    divider: ISpectDynamicColor(
      light: Colors.grey.shade300,
      dark: Colors.grey.shade800,
    ),
    logColors: {
      'error': Colors.red,
      'warning': Colors.orange,
      'info': Colors.blue,
      'debug': Colors.grey,
    },
    logIcons: {
      'error': Icons.error,
      'warning': Icons.warning,
      'info': Icons.info,
      'debug': Icons.bug_report,
    },
    logDescriptions: {
      'error': 'Critical application errors',
      'info': 'Informational messages',
    },
    disabledLogTypes: {
      'riverpod-add',
      'riverpod-update',
      'riverpod-dispose',
      'riverpod-fail',
    },
  ),
  child: child ?? const SizedBox.shrink(),
)

Panel Actions #

ISpectBuilder(
  options: ISpectOptions(
    observer: observer,
    locale: const Locale('en'),
    actionItems: [
      ISpectActionItem(
        onTap: (context) { /* Clear cache, reset state */ },
        title: 'Clear All Data',
        icon: Icons.delete_sweep,
      ),
    ],
    panelItems: [
      DraggablePanelItem(
        enableBadge: false,
        icon: Icons.settings,
        onTap: (context) { /* Open settings */ },
      ),
    ],
    panelButtons: [
      DraggablePanelButtonItem(
        icon: Icons.info,
        label: 'App Info',
        onTap: (context) { /* Show app version */ },
      ),
    ],
  ),
  child: child ?? const SizedBox.shrink(),
)

Settings Persistence #

Load saved settings on startup and persist changes via onSettingsChanged:

import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final prefs = await SharedPreferences.getInstance();
  final settingsJson = prefs.getString('ispect_settings');
  final initialSettings = settingsJson != null
      ? ISpectSettingsState.fromJson(jsonDecode(settingsJson))
      : null;

  final logger = ISpectFlutter.init();
  ISpect.run(logger: logger, () => runApp(MyApp(initialSettings: initialSettings)));
}

// In your widget:
ISpectBuilder(
  options: ISpectOptions(
    observer: observer,
    initialSettings: initialSettings ?? const ISpectSettingsState(
      enabled: true,
      useConsoleLogs: true,
      useHistory: true,
    ),
    onSettingsChanged: (settings) async {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('ispect_settings', jsonEncode(settings.toJson()));
    },
  ),
  child: child ?? const SizedBox.shrink(),
)

Callbacks #

ISpectBuilder(
  options: ISpectOptions(
    observer: observer,
    onLoadLogContent: (context) async {
      // Load log files from storage via file_picker
      return 'Loaded log content';
    },
    onOpenFile: (path) async {
      // Open with system viewer via open_filex
    },
    onShare: (ISpectShareRequest request) async {
      // Share via share_plus
    },
  ),
  child: child ?? const SizedBox.shrink(),
)
class _MyAppState extends State<MyApp> {
  final _observer = ISpectNavigatorObserver(
    isLogModals: true,
    isLogPages: true,
    isLogGestures: false,
    isLogOtherTypes: true,
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [_observer],
      builder: (_, child) => ISpectBuilder(
        observer: _observer,
        child: child ?? const SizedBox(),
      ),
    );
  }
}

Examples #

See the example/ directory for a complete working app.

Contributing #

Contributions welcome! See CONTRIBUTING.md.

License #

MIT — see LICENSE.


0
likes
150
points
1.35k
downloads

Documentation

API reference

Publisher

verified publishershodev.live

Weekly Downloads

Dio HTTP client integration for ISpect toolkit

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

ansicolor, dio, ispectify

More

Packages that depend on ispectify_dio