layerx_debugger 2.0.0
layerx_debugger: ^2.0.0 copied to clipboard
Drop-in debugger and logger for Flutter and GetX: pretty console logs, Dio/http capture, crash handling, route, widget and performance tracking, plus an in-app log viewer.
π LayerX Debugger
In-app debugging for Flutter β a new era.
Stop scrolling through console spam. See every log, API call, crash, route and rebuild β live, inside your running app β with automatic blame analysis, API response diffing and a one-line setup.
Why LayerX Debugger? #
Traditional Flutter debugging means squinting at a flood of print statements in a tiny
console, far from the device QA is actually holding. LayerX Debugger flips that β the
debugger lives inside the app. Your testers tap a floating π button and instantly see a
beautiful, searchable timeline of everything that happened, plus answers to the question that
wastes the most time on every team:
"Who owns this bug β the app, the backend, or the network?"
One call, zero boilerplate, and it never ships to your users (it's environment-aware).
await LayerXDebugger.initialize(); // β¨ that's the whole setup
β¨ What you get #
- π In-app log viewer β a draggable floating button + edge-swipe open a searchable, filterable, color-coded log list with a live session-health banner.
- π΅οΈ "Who owns this bug?" blame engine β every error is attributed to app / backend / network with a QA-ready note.
- π API response diffing β when a backend silently changes its JSON shape, LayerX shows a field-level diff (added / removed / type-changed / value-changed).
- π¨ Gorgeous console logs β
LayerXLog.d/i/w/e/swith colors, emojis & timestamps, plus boxedββ β βAPI blocks. - π Automatic network capture β
LayerXHttp(primary) logs every request/response/error with timings; secrets (password,token,authorization,apiKey,secret) are masked. - π₯ Crash capture β
FlutterError,PlatformDispatcherand zoned errors, with anonCrashhook for Crashlytics / Sentry. - π§© GetX-native β
LayerXController/LayerXService/LayerXDebugMixinlifecycle logs, and auto-registered GetX services with duplicate & double-init guards. - β± Profiling & rebuild tracking β
LayerXProfiler.measure(...),LayerXDebugWidget(tag:). - π± Environment-aware β
dev/staging/prodcontrol verbosity, colors and whether the viewer is even available.
πΈ See it in action #
The in-app Neo Terminal viewer captures everything β framework/UI exceptions,
uncaught Dart errors, print / debugPrint console output and network calls β
into one pipeline, grouped by category.
| Categorized console | Network inspector | Exception detail |
|---|---|---|
| [Categorized console log viewer] | [Network request inspector] | [Captured UI exception with stack trace and fix] |
Every log tagged by category, level & source file:line |
Status, latency & API schema-change detection | A RenderFlex overflow caught with stack trace + suggested fix |
π Install #
dependencies:
layerx_debugger: ^1.3.0
import 'package:layerx_debugger/layerx_debugger.dart';
β‘ Auto-Setup β One Command, Zero Boilerplate #
Added in v1.0.1 β the fastest way to integrate LayerX Debugger into any Flutter project.
After adding the package, just run this once from your Flutter project root:
dart run layerx_debugger:setup
That's it. The CLI will automatically:
| Step | What happens |
|---|---|
| π¦ pubspec | Adds layerx_debugger: ^1.0.1 and runs flutter pub get |
| π§ main.dart | Wraps main() with runZonedGuarded + initialize() |
| π¨ MaterialApp | Injects LayerXDebugOverlay builder + navigatorObservers |
| πΎ Backup | Creates .bak copies before modifying any file |
| π Idempotent | Safe to run multiple times β already-configured files are skipped |
Before (your original main.dart):
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: const HomeView());
}
}
After (auto-generated by the CLI):
import 'package:layerx_debugger/layerx_debugger.dart';
void main() {
LayerXDebugger.runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await LayerXDebugger.initialize(
config: const LayerXDebugConfig(appName: 'My App'),
);
runApp(const MyApp());
});
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => LayerXDebugOverlay(child: child!),
navigatorObservers: [LayerXDebugger.routeObserver],
home: const HomeView(),
);
}
}
Run the app β the floating π button appears automatically. No other changes needed.
You can also target a specific project path:
dart run layerx_debugger:setup /path/to/my_flutter_app
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:layerx_debugger/layerx_debugger.dart';
void main() {
LayerXDebugger.runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await LayerXDebugger.initialize(
config: const LayerXDebugConfig(appName: 'My App'),
);
runApp(const MyApp());
});
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
navigatorObservers: [LayerXDebugger.routeObserver], // π§ route logs
builder: (context, child) => LayerXDebugOverlay(child: child!), // π in-app viewer
home: const HomeView(),
);
}
}
Run the app in debug mode β a floating π button appears. Tap it. Done.
π Opening the in-app viewer #
There are four ways to open the viewer β pick whatever fits your app:
// 1. Floating bug button β automatic, from LayerXDebugOverlay (just tap it)
// 2. Edge swipe β swipe in from the right edge (also automatic)
// 3. From ANY button you own:
ElevatedButton(
onPressed: () => LayerXDebugger.openViewer(context),
child: const Text('Open Logs'),
);
// 4. A ready-made settings tile:
const LayerXDebugSettingsButton(),
openViewer(context)works even without the overlay β it simply pushes the viewer screen.
π Logging #
LayerXLog.d('User fetched'); // debug (grey)
LayerXLog.i('Cache warmed'); // info (blue)
LayerXLog.s('Payment captured'); // success (green)
LayerXLog.w('Retrying request'); // warning (amber)
LayerXLog.e('API error', error: e, stackTrace: st); // error (red)
LayerXLog.screen('HomeView'); // [SCREEN] HomeView opened
LayerXLog.action('Login Button Clicked'); // [ACTION] Login Button Clicked
'Saved!'.logS(); // or log any value inline
Console output:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API GET /users
β Status : 200 OK β’ 142ms
β Response : {"id":1,"name":"Ada"}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Networking β http first #
http is the primary, zero-config integration β just use LayerXHttp:
final res = await LayerXHttp.get(Uri.parse('https://api.example.com/users'));
await LayerXHttp.post(uri, headers: headers, body: jsonBody);
// get / post / put / patch / delete supported.
Already have your own http.Client? Wrap it β one line #
If your app sends every request through its own client (a custom IOClient,
a RetryClient, or a generated HttpsCalls service), you don't have to rewrite
your call sites. Wrap that one client with LayerXHttpClient and every
request β including multipart β is captured automatically:
// before
late final http.Client _client = IOClient(HttpClient()..connectionTimeout = ...);
// after β every endpoint is now logged, nothing else changes
late final http.Client _client =
LayerXHttpClient(IOClient(HttpClient()..connectionTimeout = ...));
LayerXHttpClient is a transparent http.BaseClient decorator: it never alters
your request and always returns an intact response. Because Client.get/post/ put/patch/delete and MultipartRequest.send all funnel through send, wrapping
the single client captures your whole API surface with zero per-call setup.
Dio (optional β never forced) #
LayerX has no dependency on dio. If your app uses Dio, add a tiny interceptor that
forwards to the public LayerXNetworkLogger:
class LayerXDioInterceptor extends Interceptor {
@override
void onResponse(Response res, ResponseInterceptorHandler handler) {
LayerXNetworkLogger.record(
endpoint: res.requestOptions.uri.toString(),
method: res.requestOptions.method,
statusCode: res.statusCode ?? 0,
responseBody: res.data?.toString(),
requestBody: res.requestOptions.data?.toString(),
);
handler.next(res);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
LayerXNetworkLogger.recordException(
endpoint: err.requestOptions.uri.toString(),
method: err.requestOptions.method,
error: err,
stackTrace: err.stackTrace,
);
handler.next(err);
}
}
final dio = Dio()..interceptors.add(LayerXDioInterceptor());
π§© GetX integration #
class HomeController extends LayerXController {} // lifecycle auto-logged
class AuthService extends LayerXService {}
class CartController extends GetxController with LayerXDebugMixin {} // mix into existing
initialize() auto-registers the LayerX GetX services (LayerXLoggerService,
LayerXDebugService, LayerXCrashService, LayerXNetworkService,
LayerXPerformanceService, LayerXRouteService) as permanent singletons β with
duplicate-prevention and a double-initialization guard. Modules light up incrementally as
controllers and the network layer are exercised. Set isLayerXArchitecture: false (or
autoInject: false) to skip all injection in a non-LayerX app.
π§ Route debugging #
GetMaterialApp(navigatorObservers: [LayerXDebugger.routeObserver]);
// per-page GetX middleware:
GetPage(name: '/home', page: () => HomeView(), middlewares: [LayerXRouteMiddleware()]);
π₯ Crash handling #
await LayerXDebugger.initialize(
config: LayerXDebugConfig(
onCrash: (error, stack, fatal) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: fatal);
// or: Sentry.captureException(error, stackTrace: stack);
},
),
);
Crashlytics & Sentry are optional β LayerX depends on neither and works standalone.
β± Performance & π rebuilds #
final users = await LayerXProfiler.measure('fetchUsers', () => api.fetchUsers());
LayerXProfiler.start('render'); /* ... */ LayerXProfiler.end('render');
LayerXDebugWidget(tag: 'HomeView', child: HomeView()); // logs "HomeView rebuilt N times"
βοΈ Configuration #
await LayerXDebugger.initialize(
config: LayerXDebugConfig(
appName: 'My App',
environment: LayerXEnvironment.dev, // dev / staging / prod
enableApiLogs: true,
enableRouteLogs: true,
enableCrashLogs: true,
enableGetXLogs: true,
enablePerformanceLogs: true,
enableWidgetLogs: true,
maskKeys: ['ssn', 'cardNumber'],
maxStoredLogs: 500,
edgeSwipeZone: LayerXEdgeZone.right,
autoInject: true,
isLayerXArchitecture: null, // null = auto (assume LayerX); false = skip injection
onCrash: (e, s, fatal) {/* forward */},
),
);
| Environment | Console level | Colors | In-app viewer |
|---|---|---|---|
dev |
verbose+ | β | β |
staging |
info+ | β | β |
prod |
warning+ | β | β |
π Architecture #
LayerX Debugger is itself a showcase of LayerX architecture β the source is organised into
config/, services/, repository/, mvvm/, core/ and widgets/ under lib/src/, while
lib/layerx_debugger.dart stays the single public import.
π Example #
A full GetX example lives in example/:
cd example && flutter pub get && flutter run
π€ Contributing #
Issues and PRs welcome at https://github.com/the-bughex-code/layerx_debugger.
π License #
BSD-3-Clause Β© The BugHex. See LICENSE.
