An in-app debug toolkit. Adds a button with a debug menu which is independent of the host app's widget tree. This toolkit was designed to be used in CARP Flutter apps (Android + iOS) but can be used in any Flutter app. See the CARP Debug Flutter example and CARP Debug Docs
| Tool | What it does |
|---|---|
| Environment & Servers | Switch the deployment server and override any launch argument (--dart-define) at runtime, then Apply (live reconfigure) or Restart. |
| Shared Preferences | Inspect, search, edit, add and delete any key in a SharedPreferences store (the CARP user/session data). |
| Database | Browse the local sembast database: stores, records (pretty-printed JSON), delete records / clear stores. |
| Device & App | Device + app metadata (model, OS, version, build) plus screen metrics. |
| Logs & Errors | Captured debugPrint output, framework errors and uncaught async errors. |
Everything is extensible: register your own screens via DebugTool and add
extra KeyValueStore / DebugDatabase data sources.
CarpDebugToolkit wraps the whole app in a root Stack and renders its UI
in its own scope (MediaQuery, Theme, Localizations, ScaffoldMessenger
and a dedicated Navigator). Because the overlay sits above the host
MaterialApp and never uses the host's router, a crashing screen or broken
navigation does not remove the button or the menu.
Note that however, a synchronous infinite loop on the UI isolate blocks all Dart rendering (the toolkit included). The toolkit can handle crashes, exceptions and broken navigation.
CARP apps read config such as the deployment mode with String.fromEnvironment,
which is fixed at build time. DebugEnv adds a runtime override layer that is
persisted across restarts.
// 1. Load persisted overrides before anything reads configuration.
await DebugEnv().initialize();
DebugEnv().registerAll(const [
EnvEntry(
key: 'deployment-mode',
label: 'Deployment mode',
type: EnvValueType.enumeration,
options: ['production', 'test', 'dev'],
fallback: String.fromEnvironment('deployment-mode', defaultValue: 'production'),
),
]);
// 2. Read config through DebugEnv instead of String.fromEnvironment.
final mode = DebugEnv().string('deployment-mode',
fallback: const String.fromEnvironment('deployment-mode', defaultValue: 'production'));import 'package:carp_debug_flutter/carp_debug_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
await DebugEnv().initialize(preferences: prefs);
final db = await openMySembastDatabase();
runApp(CarpDebugToolkit(
enabled: kDebugMode, // or bool.fromEnvironment('debug-toolkit', defaultValue: kDebugMode)
config: DebugToolkitConfig(
title: 'My App Debug',
envEntries: myEnvEntries,
keyValueStores: [SharedPreferencesKeyValueStore(prefs)],
databases: [SembastDebugDatabase(db, [
SembastStoreDescriptor('settings', StoreRef<String, Object?>.main()),
SembastStoreDescriptor('results', intMapStoreFactory.store('results')),
])],
onApply: () => myBackend.reconfigure(), // live reconfigure
onReinitialize: () => myBackend.reconfigure(), // before in-process restart
),
child: const MyApp(),
));
}See example/ for a complete, runnable demo with
SharedPreferences + sembast data and a custom server field.
It is good practice to not ship the toolkit to end users. To keep it out of production archives:
- Install as
dev_dependencies - Reference it only from
kDebugMode-gated code so Dart tree-shaking removes it; production code reads launch arguments through an app-local hook (String? Function(String)? debugLaunchOverride) rather than importing the package.
// main.dart — folds to `runApp(app)` in release; the wrapper (and the whole
// package) is tree-shaken away, and the dev-dependency native is excluded.
if (kDebugMode) await debug.initializeDebugTools();
runApp(kDebugMode ? debug.wrapWithDebugToolkit(app, db) : app);On Android the plugin is fully excluded from the release archive
(absent from the release GeneratedPluginRegistrant). On iOS the Dart is
excluded; the small native stub is still linked but inert (Dart never calls it)
due to flutter#163874.
You can add your own custom tools by implementing the DebugTool interface.
class MyTool implements DebugTool {
@override String get id => 'my-tool';
@override String get title => 'My Tool';
@override String? get subtitle => 'Does something useful';
@override IconData get icon => Icons.science;
@override Widget buildPage(BuildContext context) =>
const DebugScaffold(title: 'My Tool', body: Center(child: Text('Hi')));
}
// DebugToolkitConfig(extraTools: [MyTool()])See LICENSE.