ua_debug_view 1.0.0
ua_debug_view: ^1.0.0 copied to clipboard
A fully customizable, modular Flutter debug panel. Plug in only the modules you need.
ua_debug_view #
A fully modular, customizable Flutter debug panel by UserAdgents.
Plug in only the modules you need — environment switcher, network inspector, logs console, auth tokens, storage browser, and more. Drop in DebugAccountPicker straight into your login form to pre-fill credentials. Zero configuration required to get started.
Features #
- Draggable FAB — floating bug button, visible in debug builds only by default
- Multiple triggers — FAB, N-taps on any widget, long press, or shake
- 9 built-in modules — cover the most common debug needs out of the box
- Fully modular — modules activate from the parameters you pass, build your own with
CustomModule - Self-contained — fixed dark theme, no state management dependency, no generated code
Getting started #
Add the dependency:
dependencies:
ua_debug_view: ^1.0.0
Wrap your MaterialApp — zero configuration needed, App Info, Network, Logs
and Storage are active out of the box:
void main() {
runApp(
DebugPanel(
child: MaterialApp(...),
),
);
}
The other modules activate automatically when you pass their parameters:
DebugPanel(
// Environment switcher
environments: [devEnv, stagingEnv, prodEnv],
currentEnvironment: _currentEnv,
onEnvironmentSwitch: (env) async => await myEnvService.switchTo(env),
// Auth inspector
accessToken: () => myAuth.accessToken,
onLogout: () async => await myAuth.logout(),
child: MaterialApp(...),
)
Triggers #
Four ways to open the panel — combine them freely:
// 1. Automatic draggable FAB (default)
DebugPanel(child: MaterialApp(...))
// 2. Tap N times on any widget (e.g. your logo)
DebugTrigger(
tapCount: 5,
modules: [AppInfoModule(), NetworkModule()],
child: MyLogoWidget(),
)
// 3. Long press
DebugTrigger.longPress(modules: [...], child: MyWidget())
// 4. Shake the device
DebugShakeTrigger(modules: [...], child: MyApp())
Unlike DebugPanel, the standalone triggers take an explicit modules: list —
construct the module classes directly (AppInfoModule(), NetworkModule(),
LogsModule(), …).
Control visibility per build mode:
DebugPanel(
visibility: DebugVisibility.debugOnly, // default
// DebugVisibility.debugAndProfile
// DebugVisibility.always
// DebugVisibility.never
child: MaterialApp(...),
)
Modules #
Always active with DebugPanel: App Info, Network, Logs, Storage.
Activated by their parameters: Environment, Auth, Actions, Design System.
Custom modules go in extraModules.
Each module class is also public — construct it directly when using a
standalone DebugTrigger / DebugShakeTrigger.
AppInfoModule #
Displays version, build number, and bundle ID (auto-read from
package_info_plus), plus any extra key/value pairs:
DebugPanel(
appInfoExtras: {'Git SHA': 'a3f5c2', 'Built at': '2026-04-03'},
child: MaterialApp(...),
)
EnvironmentModule #
Switch between environments with a confirmation dialog. The active environment badge appears on the FAB automatically.
DebugPanel(
environments: [
DebugEnvironment(
name: 'Development',
tag: 'DEV',
color: Colors.green,
values: {'Base URL': 'https://dev.api.example.com'},
),
DebugEnvironment(name: 'Staging', tag: 'STG', color: Colors.orange),
DebugEnvironment(name: 'Production', tag: 'PROD', color: Colors.red),
],
currentEnvironment: myEnvService.current,
onEnvironmentSwitch: (env) async => await myEnvService.switchTo(env),
environmentShowConfirmDialog: true, // default
child: MaterialApp(...),
)
AuthModule #
Displays access token, refresh token, expiry, and user info. One-tap copy on any token. Optional logout button.
DebugPanel(
accessToken: () => myAuth.accessToken, // activates the module
refreshToken: () => myAuth.refreshToken, // optional
tokenExpiry: () => myAuth.expiry, // optional, DateTime
authAdditionalInfo: {
'Email': () => myAuth.userEmail,
'Role': () => myAuth.role,
},
onLogout: () async => await myAuth.logout(), // optional
child: MaterialApp(...),
)
DebugAccountPicker #
A drop-in widget you place inside your login form. Tapping a test account calls onSelected so you can fill your form fields — the user then submits using your normal "Sign in" button. Auto-hides outside debug builds.
onSelected is form-library agnostic — it just hands you the TestAccount, you fill the fields however you like (see Filling the fields below). Need a bottom sheet instead of an inline widget? See As a bottom sheet.
Works standalone — no DebugPanel required. Just drop it into the form and you're done.
// Anywhere convenient — alongside the screen, in a const file, etc.
const _testAccounts = [
TestAccount(
id: 'user@dev.com',
password: '1234',
label: 'Standard user',
info: 'bronze loyalty',
),
TestAccount(
id: 'admin@dev.com',
password: 'admin',
label: 'Dev-only admin',
info: 'full access',
),
TestAccount(
id: '+33612345678',
password: '1234',
label: 'Phone login',
),
];
// Inside your login screen's build():
Column(
children: [
DebugAccountPicker(
accounts: _testAccounts,
onSelected: (acc) {
_idController.text = acc.id;
_passwordController.text = acc.password;
},
// accentColor: Colors.purple, // optional override
),
TextField(controller: _idController),
TextField(controller: _passwordController, obscureText: true),
ElevatedButton(onPressed: _login, child: const Text('Sign in')),
],
)
Filling the fields
onSelected doesn't assume any form library. Two common ways:
// 1. Raw TextEditingControllers
onSelected: (acc) {
_idController.text = acc.id;
_passwordController.text = acc.password;
},
// 2. flutter_form_builder — no controller needed, drive the fields
// straight from the form key:
onSelected: (acc) {
final fields = _formKey.currentState?.fields;
fields?['email']?.didChange(acc.id);
fields?['password']?.didChange(acc.password);
},
As a bottom sheet
When the inline widget doesn't fit your layout — it lives inside a Row, a horizontally-scrolling list, an IntrinsicHeight, or any context that doesn't give it a bounded height — rendering inline can break layout. Open the picker in a modal bottom sheet instead: the list lives in its own route with its own constraints, so it never touches your form's layout.
Use the ready-made button (self-hides with the same rules as the inline picker):
DebugAccountPickerButton(
accounts: _testAccounts,
onSelected: (acc) {
_idController.text = acc.id;
_passwordController.text = acc.password;
},
// label: 'Pick a test account', // optional
)
…or trigger the sheet yourself from any callback:
final picked = await DebugAccountPicker.showAsSheet(
context,
accounts: _testAccounts,
onSelected: (acc) { /* fill your fields */ },
);
// `picked` is the selected TestAccount, or null if dismissed.
TestAccount fields:
| Field | Required | Purpose |
|---|---|---|
id |
yes | Identifier passed to your form (email, phone, username — whatever) |
password |
yes | Password matching id |
label |
no | Human-readable name (e.g. "Alice — admin"). Falls back to id. |
info |
no | Free-form info shown below the label (e.g. "Hybris ✓ · Comarch ✗") |
environments |
no | Per-environment filter — see below. Only effective when wrapped by DebugPanel. |
Optional integration with DebugPanel — if a DebugPanel happens to wrap your app, the picker automatically:
- inherits its
accentColor(override with theaccentColorprop on the picker if needed); - respects its
DebugVisibilitysetting (e.g.neverhides the picker too); - filters accounts by the active
DebugEnvironment— accounts whoseenvironmentslist is non-empty only show when one of those envs is active. Useful fordev-only orstaging-only credentials.
Without DebugPanel, all accounts are shown in debug builds (environments is ignored), and the default blue accent is used unless overridden.
NetworkModule #
Captures all HTTP requests and responses in a Charles Proxy-style list. Tap any request to see full details.
// Enable interception in main():
void main() {
DebugView.enableNetworkCapture();
runApp(...);
}
// Configure via DebugPanel (module is always active):
DebugPanel(
networkMaxRequests: 100,
networkIgnoredPaths: ['/healthcheck'],
child: MaterialApp(...),
)
enableNetworkCapture() preserves and chains any HttpOverrides your app
already installed (proxy, certificate pinning…), and calling it twice is a
no-op. If you need lower-level control, install DebugHttpOverrides yourself:
HttpOverrides.global = DebugHttpOverrides().
You can also add requests manually (useful with Dio or other clients):
DebugNetworkStore.instance.add(
DebugNetworkRequest(
timestamp: DateTime.now(),
method: 'POST',
url: 'https://api.example.com/login',
statusCode: 200,
duration: Duration(milliseconds: 312),
responseBody: '{"token": "..."}',
),
);
LogsModule #
Filterable log console with levels (verbose, debug, info, warning, error) and tags.
The recommended approach is to pipe your existing logger's stream — the panel stays a passive observer, with no coupling to your app code:
DebugPanel(
logStream: myLogger.stream,
logsMaxEntries: 500, // default
child: MaterialApp(...),
)
If you don't have a logging infrastructure yet, DebugLogger is a built-in lightweight option used as fallback when no logStream is provided:
// Emit logs from anywhere in your app:
DebugLogger.v('Verbose message');
DebugLogger.d('Debug message', tag: 'AUTH');
DebugLogger.i('Info message', tag: 'NETWORK');
DebugLogger.w('Warning');
DebugLogger.e('Error occurred');
Note: Avoid calling
DebugLoggerdirectly in production app code — it couples your business logic to the debug panel. Prefer piping an existing stream.
StorageModule #
Browse all SharedPreferences keys. Sensitive keys are masked automatically. Supports additional custom storage providers.
DebugPanel(
storageSensitiveKeys: ['token', 'password', 'secret'],
storageAdditional: [
DebugStorageProvider(
name: 'Secure Storage',
read: () async => await mySecureStorage.readAll(),
),
],
child: MaterialApp(...),
)
ActionsModule #
One-tap debug actions: clear cache, reset onboarding, trigger a crash, etc. Optional confirmation dialog per action. Toggle switches for boolean flags.
DebugPanel(
// Toggle switches — shown in a dedicated "Toggles" section
debugToggles: [
DebugToggleAction(
label: 'Dark mode',
icon: Icons.dark_mode_outlined,
initialValue: isDarkMode,
onToggle: (value) async => setDarkMode(value),
),
DebugToggleAction(
label: 'Show grid overlay',
icon: Icons.grid_on_outlined,
initialValue: false,
onToggle: (value) async => setGridOverlay(value),
),
],
// Buttons — shown in an "Available Actions" section
debugActions: [
DebugAction(
label: 'Clear cache',
icon: Icons.delete_outline,
onTap: () async => await CacheService.clear(),
),
DebugAction(
label: 'Reset onboarding',
icon: Icons.replay,
requiresConfirmation: true,
onTap: () async => await OnboardingService.reset(),
),
],
child: MaterialApp(...),
)
Both debugActions and debugToggles are optional — the module activates as
soon as one of them is provided.
DesignSystemModule #
Preview pages for your app's design tokens — colors, typography, components.
DebugPanel(
designSystemSections: [
DesignSystemSection(
title: 'Colors',
builder: (context) => MyColorPaletteWidget(),
),
DesignSystemSection(
title: 'Typography',
builder: (context) => MyTypographyWidget(),
),
],
child: MaterialApp(...),
)
CustomModule #
Fully custom module — supply your own title, icon, and widget. No contract to implement beyond that. Register it via extraModules:
DebugPanel(
extraModules: [
CustomModule(
title: 'Feature Flags',
icon: Icons.flag_outlined,
builder: (context) => MyFeatureFlagsWidget(),
),
],
child: MaterialApp(...),
)
Build your own module #
Implement DebugModule to create a reusable module:
class MyModule extends DebugModule {
const MyModule();
@override
String get title => 'My Module';
@override
IconData get icon => Icons.star_outline;
@override
Widget buildPage(BuildContext context) => const MyModulePage();
// Optional: inline preview shown in the menu
@override
Widget? buildPreview(BuildContext context) => const Text('Quick info here');
}
Register it like any custom module — extraModules: [MyModule()] on
DebugPanel, or in the modules: list of a trigger.
Accent color #
Override the default blue accent for the FAB and the panel:
DebugPanel(
accentColor: const Color(0xFF9B59B6),
child: MaterialApp(...),
)
Dependencies #
| Package | Usage |
|---|---|
package_info_plus |
App version & bundle ID in AppInfoModule |
shared_preferences |
Storage browsing in StorageModule |
sensors_plus |
Shake detection in DebugShakeTrigger |
License #
MIT — see LICENSE.