Flutter Dev Toolkit

πŸš€ A modular in-app developer console for Flutter apps.

Track logs, API calls, navigation, lifecycle events, screen transitions, app state, and more β€” all in real time, inside your app.

pub package GitHub


✨ Features

  • βœ… In-app Dev Console with a draggable floating overlay
  • βœ… Colored logs with filtering and tagging
  • βœ… Network call inspector (supports http, dio, and retrofit)
  • βœ… Route stack and screen duration tracker
  • βœ… Search and filtering β€” by level and tag in logs, by method and status class in network
  • βœ… Crash reporter β€” Flutter and unhandled async errors with stack traces
  • βœ… Performance monitor β€” FPS, memory (RSS), startup time, jank frames, and a rolling history sparkline
  • βœ… Deep link inspector with query parameter breakdown
  • βœ… Storage inspector β€” view, add, edit and delete SharedPreferences entries live
  • βœ… Runtime feature flags β€” flip app-registered flags without a rebuild
  • βœ… Network response mocking β€” short-circuit a request with a canned status/body/delay
  • βœ… Lifecycle event logging
  • βœ… Device info panel
  • βœ… Export logs, network calls (JSON, cURL, HAR 1.2) and route data
  • βœ… Plugin system for adding custom tools
  • βœ… App State Inspector (Bloc built in, Riverpod/Provider in a few lines)
  • βœ… Light and dark console themes
  • βœ… Suppressed in release builds by default

πŸ›  Installation

Add to your pubspec.yaml:

dependencies:
  flutter_dev_toolkit: ^latest_version

Then:

flutter pub get

πŸš€ Getting Started

1. Initialize the toolkit

void main() {
  FlutterDevToolkit.init(
    config: DevToolkitConfig(
      logger: DefaultLogger(),

      // All built-in plugins are enabled by default. List the ones you want
      // to leave out:
      disableBuiltInPlugins: [
        // BuiltInPluginType.logs,
        // BuiltInPluginType.network,
        // BuiltInPluginType.routes,
        // BuiltInPluginType.deviceInfo,
        // BuiltInPluginType.crashes,
        // BuiltInPluginType.performance,
        // BuiltInPluginType.deepLinks,
        // BuiltInPluginType.storage,
        // BuiltInPluginType.featureFlags,
      ],
    ),
  );

  runApp(MyApp());
}

init() installs FlutterError.onError and PlatformDispatcher.onError handlers so the Crashes tab can record errors. If you install your own handler afterwards, chain to the previous one β€” replacing it outright silently stops crash capture:

final previousOnError = FlutterError.onError;
FlutterError.onError = (details) {
  myCrashReporter.record(details);
  previousOnError?.call(details);
};

Configuration options

Option Default Purpose
logger β€” The LoggerInterface backing the Logs tab
disableBuiltInPlugins [] Built-in plugins to leave out
enableInRelease false Whether the overlay is active in release builds
maxLogEntries 2000 Log entries retained in memory
maxNetworkLogs 500 Network calls retained in memory
logFrameDrops false Also write every dropped frame to the Logs tab
theme DevConsoleTheme.dark Console's starting theme; toggleable at runtime

Release builds

The toolkit is suppressed in release builds unless you opt in with enableInRelease: true. When suppressed, DevOverlay renders nothing and FlutterDevToolkit.logger becomes a no-op, so your logging calls stay safe in production.

2. Add Dev Console Overlay

MaterialApp(
  builder: (context, child) {
    return Stack(
      children: [
        child!,
        const DevOverlay(),
      ],
    );
  },
  navigatorObservers: [RouteInterceptor.instance],
);

πŸ”Œ Network Setup

πŸ”Ή Using http package

Replace the default client with HttpInterceptor from the toolkit:

import 'package:http/http.dart' as http;
import 'package:flutter_dev_toolkit/flutter_dev_toolkit.dart';

final client = HttpInterceptor(); // Instead of http.Client()

final response = await client.get(Uri.parse('https://example.com'));

πŸ”Ή Using dio

Register Dio interceptor:

final dio = Dio();
dio.interceptors.add(DioNetworkInterceptor());

πŸ”Ή Using retrofit

Pass the configured Dio instance to your Retrofit client:

final api = MyApiClient(Dio()..interceptors.add(DioNetworkInterceptor()));

Mocking a response

Manage mock rules from the Network tab's config panel (the tune icon in its app bar), or add one directly:

NetworkMockStore.add(NetworkMockRule(
  urlContains: '/api/users',   // case-insensitive substring match
  method: 'GET',               // null matches any method
  statusCode: 200,
  responseBody: '{"id": 1, "name": "Ada"}',
  delay: Duration(milliseconds: 300), // simulate latency; defaults to none
));

A matching request never reaches the network β€” both HttpInterceptor and DioNetworkInterceptor short-circuit it β€” and shows up in the Network tab tagged MOCKED. The first enabled rule that matches wins.


🧩 Plugins

You can add custom developer tools as plugins:

class CounterPlugin extends DevToolkitPlugin {
  @override 
  String get name => 'Counter';

  @override 
  IconData get icon => Icons.exposure_plus_1;

  @override 
  void onInit() => debugPrint('CounterPlugin loaded!');

  @override 
  Widget buildTab(BuildContext context) => Center(child: Text('Counter Tab'));
}

FlutterDevToolkit.registerPlugin(CounterPlugin());

Deep link capture is routing-agnostic β€” call the observer wherever your app receives a link:

// uni_links
uriLinkStream.listen((uri) {
  if (uri != null) {
    DevToolkitDeepLinkObserver.onLinkReceived(uri.toString());
  }
});

// go_router
GoRouter(
  redirect: (context, state) {
    DevToolkitDeepLinkObserver.onLinkReceived(
      state.uri.toString(),
      source: 'go_router',
    );
    return null;
  },
);

πŸ—„οΈ Storage Inspector

The Storage tab reads and writes the app's SharedPreferences directly β€” no setup needed beyond enabling the plugin (it's on by default). Add, edit, and delete entries of any type SharedPreferences supports (bool, int, double, String, List<String>), with search and JSON export.

Since SharedPreferences has no change notifications of its own, the tab loads a snapshot on open and after every edit made through it; it won't pick up a write your app makes directly while the tab happens to be open β€” use the refresh button for that.


🚩 Feature Flags

Register a flag once, anywhere in your app, and it shows up in the Flags tab β€” flip it at runtime, no rebuild:

final showNewCheckout = FeatureFlagStore.register(FeatureFlag(
  key: 'new_checkout_flow',
  label: 'New Checkout Flow',
  type: FeatureFlagType.boolean,
  defaultValue: false,
));

// Anywhere that needs to react to it:
ValueListenableBuilder(
  valueListenable: showNewCheckout.notifier,
  builder: (_, enabled, __) =>
      (enabled as bool) ? const NewCheckout() : const OldCheckout(),
);

FeatureFlagType also supports string, number, and options (a fixed set of values, via FeatureFlag.options). Re-registering the same key β€” safe to do on every main() run, including hot reload β€” returns the existing flag rather than resetting whatever it's currently set to.


πŸ” App State Inspector

Inspect state transitions, showing each change's previous and current value.

Bloc works out of the box, since the toolkit already depends on bloc:

Bloc.observer = DevBlocObserver();

FlutterDevToolkit.registerPlugin(
  AppStateInspectorPlugin([
    BlocAdapter(),
  ]),
);

Other state frameworks

Rather than depend on every state library, the toolkit accepts changes pushed in from your app through RecordedStateAdapter. Each framework is a few lines.

Riverpod:

final riverpodInspector = RecordedStateAdapter(name: 'Riverpod');

class DevToolkitProviderObserver extends ProviderObserver {
  @override
  void didUpdateProvider(provider, previousValue, newValue, container) {
    riverpodInspector.record(
      provider.name ?? provider.runtimeType.toString(),
      newValue,
      previous: previousValue,
    );
  }
}

runApp(
  ProviderScope(
    observers: [DevToolkitProviderObserver()],
    child: MyApp(),
  ),
);

Provider / ChangeNotifier:

final providerInspector = RecordedStateAdapter(name: 'Provider');

class CartModel extends ChangeNotifier {
  CartModel() {
    addListener(() => providerInspector.record('CartModel', items));
  }
}

Then register whichever adapters you use:

FlutterDevToolkit.registerPlugin(
  AppStateInspectorPlugin([BlocAdapter(), riverpodInspector]),
);

πŸ“ Logging

FlutterDevToolkit.logger.log('Message');
FlutterDevToolkit.logger.log('Error occurred', level: LogLevel.error);

πŸ“€ Exporting

You can export relevant data directly from each plugin’s tab:

  • Logs Plugin β†’ Export filtered logs
  • Network Plugin β†’ Export captured network calls as JSON, as a runnable cURL command, or as a HAR 1.2 archive you can open in Chrome DevTools, Postman or Charles Proxy
  • Route Tracker β†’ Export route stack and navigation history
  • Crashes β†’ Export captured errors with stack traces
  • Deep Links β†’ Export recorded links as JSON
  • Storage β†’ Export all SharedPreferences entries as JSON
  • Flags β†’ Export all registered feature flags and their current values as JSON

πŸ“„ License

MIT

Libraries

built_in_plugins/crash_plugin
built_in_plugins/device_info_plugin
built_in_plugins/feature_flags_plugin
built_in_plugins/logs_plugin
built_in_plugins/network_plugin
built_in_plugins/performance_plugin
built_in_plugins/routes_plugin
built_in_plugins/storage_plugin
built_in_plugins/widgets/crash_tab
built_in_plugins/widgets/device_info_tab
built_in_plugins/widgets/feature_flags_tab
built_in_plugins/widgets/log_tile_widget
built_in_plugins/widgets/logs_tab
built_in_plugins/widgets/network_mock_panel
built_in_plugins/widgets/network_tab
built_in_plugins/widgets/performance_tab
built_in_plugins/widgets/routes_tab
built_in_plugins/widgets/storage_tab
core/crash_log_store
core/default_logger
core/dev_console_theme
core/dev_toolkit_config
core/dev_toolkit_plugin
core/device_info_store
core/feature_flag_store
core/logger_interface
core/network_log_store
core/platform_probe
core/platform_probe_io
core/platform_probe_stub
core/plugin_registry
core/share_utils
core/storage_inspector_store
flutter_dev_toolkit
interceptors/interceptor_registry
interceptors/lifecycle_interceptor
interceptors/network/dio_interceptor
interceptors/network/http_interceptor
interceptors/network/network_log
interceptors/network/network_mock_rule
interceptors/network/network_mock_store
interceptors/network_interceptor
interceptors/performance/cold_start_timer
interceptors/performance/frame_drop_detector
interceptors/performance/memory_probe
interceptors/performance/memory_probe_io
interceptors/performance/memory_probe_stub
interceptors/performance/performance_history
interceptors/route_interceptor
models/built_in_plugin_type
models/crash_entry
models/deep_link_entry
models/log_entry
models/log_tag
models/route_entry
plugins/adapters/bloc_adapter
plugins/state_inspector/app_state_adapter
plugins/state_inspector/app_state_entry
plugins/state_inspector/app_state_inspector_plugin
plugins/state_inspector/bloc_state_tracker
plugins/state_inspector/recorded_state_adapter
ui/dev_toolkit_tab_sync
ui/log_console
ui/log_overlay
ui/network_log_detail_page