Skip to content

Quickstart

To make sure the toolkit is excluded from production archives you need to setup three parts: a launch-argument hook, a debug-only setup file, and a kDebugMode gate in main().

Create a file with no dependency on carp_debug_flutter. Production code reads launch arguments through this hook, so it never imports the package.

lib/debug/debug_hooks.dart
String? Function(String key)? debugLaunchOverride;

The read the configuration anywhere in your app like the following: (in release the hook is null, so the compile-time value is used):

final mode = debugLaunchOverride?.call('deployment-mode')
?? const String.fromEnvironment('deployment-mode', defaultValue: 'production');

This is the only file that imports the package. The two tabs differ only in where the preferences and database come from.

lib/debug/debug_setup.dart
// ignore_for_file: depend_on_referenced_packages
import 'package:carp_debug_flutter/carp_debug_flutter.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart' show Settings;
import 'package:flutter/widgets.dart';
import 'package:sembast/sembast.dart';
import 'debug_hooks.dart';
Future<void> initDebugTools() async {
await DebugEnv().initialize();
DebugEnv().registerAll(myEnvEntries);
debugLaunchOverride = DebugEnv().overrideOf; // install the hook
}
Widget wrapWithToolkit(Widget child, Database db) => CarpDebugToolkit(
config: DebugToolkitConfig(
title: 'My CARP App Debug',
envEntries: myEnvEntries,
keyValueStores: [
SharedPreferencesKeyValueStore(Settings().preferences!,
name: 'CARP Preferences'),
],
databases: [SembastDebugDatabase(db, [
SembastStoreDescriptor('settings', StoreRef<String, Object?>.main()),
SembastStoreDescriptor('results', intMapStoreFactory.store('result_store')),
])],
onApply: () => bloc.backend.reconfigure(), // re-point CAWS live
onReinitialize: () => bloc.backend.reconfigure(), // iOS restart path
),
child: child,
);

main() imports only the app-local debug_setup.dart, and calls it solely when kDebugMode. In release this folds to runApp(app) and the whole debug subtree (and the package) is tree-shaken away.

import 'package:flutter/foundation.dart';
import 'debug/debug_setup.dart' as debug;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load overrides BEFORE the app reads its configuration.
if (kDebugMode) await debug.initDebugTools();
final db = await openAppDatabase();
final app = await buildApp(db);
runApp(kDebugMode ? debug.wrapWithToolkit(app, db) : app);
}

myEnvEntries is a list of EnvEntry descriptors i.e. the values you normally pass via --dart-define:

const myEnvEntries = <EnvEntry>[
EnvEntry(
key: 'deployment-mode',
label: 'Deployment mode',
group: 'Server',
type: EnvValueType.enumeration,
options: ['production', 'test', 'dev'],
fallback: String.fromEnvironment('deployment-mode', defaultValue: 'production'),
),
EnvEntry(key: 'server-host', label: 'App server host', group: 'Server'),
];

You can either tap the bug icon in the app or just open and close it programmatically:

DebugController.instance.open();
DebugController.instance.close();
DebugController.instance.toggle();