ispect 5.2.0-dev.26 copy "ispect: ^5.2.0-dev.26" to clipboard
ispect: ^5.2.0-dev.26 copied to clipboard

In-app observability and QA diagnostics toolkit for Flutter, with logs, network tracing, layout inspection, exports, and redaction.

example/lib/main.dart

// ISpect quick start.
//
// Run with the toolkit enabled:
//   flutter run --dart-define=ISPECT_ENABLED=true
//
// Without that flag every entry point is a const no-op and tree-shakes away
// from release builds.
//
// What this file shows:
//   • Guarded startup via ISpect.run (FlutterError + zone error handlers).
//   • Every ISpect.logger level (info/good/warning/error/debug/critical/…).
//   • The standalone JSON viewer screen.
//   • The HTTP composer ("mini-Postman") — wired through onPickComposerFile.
//   • Environment metadata in exported logs — wired through metadataProvider.
//
// For a deeper tour (custom themes, locales, Dio/HTTP/WS/DB interceptors,
// Riverpod/Bloc observers, stress tests, compact network URLs, jank logging)
// run `lib/complex_example.dart`.

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

// Forward ISpect events to your crash reporter / analytics.
// Wire it into ISpect.run(logger: ISpectFlutter.init(observer: SentryISpectObserver())).
class SentryISpectObserver implements ISpectObserver {
  @override
  void onLog(ISpectLogData data) {/* Sentry.addBreadcrumb(...) */}
  @override
  void onError(ISpectLogData data) {/* Sentry.captureException(...) */}
  @override
  void onException(ISpectLogData data) {/* Sentry.captureException(...) */}
}

void main() {
  // ISpect.run wraps the app in a guarded Zone and installs error handlers
  // (FlutterError, PlatformDispatcher, runZonedGuarded).
  ISpect.run(
    () => runApp(const MyApp()),
    // Provide a custom logger if you need a non-default ISpectFlutter setup.
    // logger: ISpectFlutter.init(observer: SentryISpectObserver()),
    //
    // Lifecycle hooks fire before/after the zoned callback.
    // onInit: () {},
    // onInitialized: () {},
    //
    // Filter out noisy log messages by substring match.
    // filters: ['Heartbeat', 'metrics.tick'],
    //
    // Forward uncaught zone errors to Sentry, Crashlytics, etc.
    // onZonedError: (e, st) => Sentry.captureException(e, stackTrace: st),
  );
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Keep the observer in State so it survives rebuilds — it captures route
  // history for the ISpect panel and must not be re-created per build.
  final _ispectObserver = ISpectNavigatorObserver();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ISpect Quick Start',
      navigatorObservers: ISpectNavigatorObserver.observers(
        observer: _ispectObserver,
        additional: [
          // Your custom NavigatorObservers (analytics, deep links, etc.)
        ],
      ),
      localizationsDelegates: [
        // Your app delegates (e.g. GlobalMaterialLocalizations.delegate)
        ...ISpectLocalizations.delegate(),
      ],
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      builder: (_, child) => ISpectBuilder.wrap(
        child: child!,
        // Set to false to hide the panel for non-admin users at runtime.
        // Compile-time gating already happens via kISpectEnabled.
        // isISpectEnabled: currentUser.isAdmin,
        //
        // Custom theme for the ISpect UI (colors, log icons, page title,
        // custom log types, panel theme).
        // theme: const ISpectTheme(
        //   pageTitle: 'My Diagnostics',
        //   primary: ISpectDynamicColor(
        //     light: Color(0xFF1565C0),
        //     dark: Color(0xFF64B5F6),
        //   ),
        //   // logColors: {'http-request': Colors.blue},
        //   // logIcons: {'http-request': Icons.cloud_upload},
        //   // customLogTypes: [...],
        // ),
        options: ISpectOptions(
          observer: _ispectObserver,
          // Default locale of the ISpect UI.
          locale: const Locale('en'),
          // Toggle individual tools off if you don't need them.
          isLogPageEnabled: true,
          isPerformanceEnabled: true,
          isInspectorEnabled: true,
          isColorPickerEnabled: true,
          // Route severe-jank frames into the log viewer. Off by default to
          // avoid spam; severeJankFactor (default 2.0) sets the threshold.
          enableJankLogging: false,
          // severeJankFactor: 2.0,
          metadataProvider: _buildMetadata,
          onPickComposerFile: _pickComposerFile,
          onOpenFile: (path) async {
            // Open exported log files (e.g. via package:open_filex).
          },
          onShare: (request) async {
            // Send sessions/logs out (e.g. via package:share_plus).
            // request.text / request.subject / request.filePaths
          },
          onLoadLogContent: (context) async {
            // Return raw text from a file/clipboard when importing a session.
            // Returning null cancels without an error.
            return null;
          },
          onSettingsChanged: (settings) {
            // Here you can save changed settings to local storage
            // e.g. prefs.setString('ispect', jsonEncode(settings.toJson()));
          },
          // Here you can attach saved settings from local storage
          initialSettings: null,
          // Add custom buttons to the action sheet (logs export, share, etc.).
          actionItems: [
            ISpectActionItem(
              title: 'Reset cache',
              icon: Icons.refresh,
              description: 'Clears local cache and reloads',
              onTap: (context) => ISpect.logger.info('Cache reset requested'),
            ),
          ],
          // Extra buttons on the bottom of the draggable panel.
          panelButtons: const [
            // DraggablePanelButtonItem(icon: Icons.bug_report, label: 'Bug', onTap: ...),
          ],
          // Icon-only items on the draggable panel.
          panelItems: const [
            // DraggablePanelItem(icon: Icons.cookie, onTap: ...),
          ],
          // Replace the whole draggable panel (see ISpectPanelData / panelBuilder).
          // panelBuilder: (context, data) => DraggablePanel(
          //   controller: data.controller,
          //   items: data.items,
          //   buttons: data.buttons,
          //   theme: data.theme,
          //   child: data.child,
          // ),
          //
          // Plug in custom inspector pages (see InspectorPlugin).
          plugins: const [],
          // Replace the default log card rendering completely.
          // logBuilder: (context, log) => MyCustomLogCard(log: log),
        ),
      ),
      home: const _HomePage(),
    );
  }

  // Environment metadata embedded into the header of exported/shared logs.
  // ISpect never collects these itself — supply values you already own
  // (package_info_plus / device_info_plus / --dart-define). Resolution may be
  // async; keep tokens and PII out, the block is written verbatim.
  ISpectMetadata _buildMetadata() => const ISpectMetadata(
        appName: 'ISpect Quick Start',
        appVersion: '1.0.0',
        buildNumber: '1',
        environment: 'dev',
      );

  // Supplies a file for multipart bodies in the HTTP composer. A real app wires
  // file_picker / image_picker here; the stub returns in-memory bytes so the
  // "attach file" control works without a native picker. Omit this callback to
  // hide that control entirely.
  Future<ComposerPickedFile?> _pickComposerFile() async => ComposerPickedFile(
        filename: 'sample.txt',
        bytes: 'Hello from the ISpect HTTP composer'.codeUnits,
        contentType: 'text/plain',
      );
}

class _HomePage extends StatelessWidget {
  const _HomePage();

  @override
  Widget build(BuildContext context) {
    // All log methods are static on ISpect.logger. They all become no-ops
    // when kISpectEnabled is false, so feel free to scatter them in code.
    final logger = ISpect.logger;

    // Adapter packages (add the one(s) you need to pubspec.yaml):
    //   ispectify_dio    — Dio interceptor
    //   ispectify_http   — package:http interceptor
    //   ispectify_ws     — WebSocket logger
    //   ispectify_bloc   — Bloc.observer = ISpectBlocObserver()
    //   ispectify_db     — Database tracing (Hive, SharedPreferences, …)

    return Scaffold(
      appBar: AppBar(title: const Text('ISpect Quick Start')),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
        children: [
          const _SectionTitle('Log levels'),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              FilledButton(
                onPressed: () => logger.info('Hello from ISpect!'),
                child: const Text('info'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.good('Order placed successfully'),
                child: const Text('good'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.warning('Cache nearly full'),
                child: const Text('warning'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.debug('Reached checkout step 2'),
                child: const Text('debug'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.error(
                  'Something went wrong',
                  exception: StateError('Demo error'),
                  stackTrace: StackTrace.current,
                ),
                child: const Text('error'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.critical('Payment gateway unreachable'),
                child: const Text('critical'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.route('/home → /details'),
                child: const Text('route'),
              ),
              FilledButton.tonal(
                onPressed: () => logger.track(
                  'sign_in_tapped',
                  event: 'ui',
                  analytics: 'amplitude',
                  parameters: const {'screen': 'home'},
                ),
                child: const Text('track'),
              ),
              FilledButton.tonal(
                onPressed: () => _demoHandledException(logger),
                child: const Text('handle'),
              ),
            ],
          ),
          const SizedBox(height: 24),
          const _SectionTitle('Tools'),
          OutlinedButton.icon(
            onPressed: () => _openJsonViewer(context),
            icon: const Icon(Icons.data_object),
            label: const Text('Open JSON viewer'),
          ),
          const SizedBox(height: 24),
          const _SectionTitle('Inside the panel'),
          const Text(
            'Tap the floating ISpect button to open the panel:\n'
            '• Logs — filter, search, export, import, share.\n'
            '• HTTP composer (api icon) — replay or craft a request; the '
            '"attach file" control appears because onPickComposerFile is set.\n'
            '• Performance, widget inspector, color picker.',
          ),
          // Other available methods:
          //   logger.verbose / provider / print / log / logData
          //
          // Drive the panel from any widget under ISpectBuilder:
          //   final scope = ISpect.read(context);
          //   scope.toggleISpect();              // show / hide the panel
          //   scope.togglePerformanceTracking(); // FPS overlay
          //   scope.options = scope.options.copyWith(locale: Locale('ru'));
        ],
      ),
    );
  }

  void _demoHandledException(ISpectLogger logger) {
    try {
      throw const FormatException('Malformed payload');
    } catch (e, st) {
      logger.handle(exception: e, stackTrace: st, message: 'Parse failed');
    }
  }

  void _openJsonViewer(BuildContext context) {
    const sample = <String, dynamic>{
      'user': {
        'id': 42,
        'name': 'Ada Lovelace',
        'roles': ['admin', 'editor'],
        'active': true,
      },
      'orders': [
        {'id': 'A-1', 'total': 19.99},
        {'id': 'A-2', 'total': 4.50},
      ],
      'meta': {
        'version': '5.2.0',
        'nested': {
          'deep': {'value': null}
        }
      },
    };
    const JsonScreen(data: sample).push(context);
  }
}

class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Text(
        text,
        style: Theme.of(context).textTheme.titleMedium,
      ),
    );
  }
}
31
likes
160
points
6.52k
downloads

Documentation

API reference

Publisher

verified publishershodev.live

Weekly Downloads

In-app observability and QA diagnostics toolkit for Flutter, with logs, network tracing, layout inspection, exports, and redaction.

Repository (GitHub)
View/report issues
Contributing

Topics

#inspector #ispect #debug #toolkit #debug-toolkit

License

MIT (license)

Dependencies

collection, draggable_panel, flutter, flutter_localizations, intl, ispect_layout, ispectify, meta, path_provider, super_sliver_list, web

More

Packages that depend on ispect