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.
β¨ Features
- β In-app Dev Console with a draggable floating overlay
- β Colored logs with filtering and tagging
- β
Network call inspector (supports
http,dio, andretrofit) - β 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 Links
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/deep_link_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/deep_link_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/deep_link_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/storage_inspector_store
- flutter_dev_toolkit
- interceptors/deep_link_observer
- 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