🐛 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.

pub version pub points platforms license getx


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 logsLayerXLog.d/i/w/e/s with colors, emojis & timestamps, plus boxed ┌─ │ └ API blocks.
  • 🌐 Automatic network captureLayerXHttp (primary) logs every request/response/error with timings; secrets (password, token, authorization, apiKey, secret) are masked.
  • 💥 Crash captureFlutterError, PlatformDispatcher and zoned errors, with an onCrash hook for Crashlytics / Sentry.
  • 🧩 GetX-nativeLayerXController / LayerXService / LayerXDebugMixin lifecycle logs, and auto-registered GetX services with duplicate & double-init guards.
  • Profiling & rebuild trackingLayerXProfiler.measure(...), LayerXDebugWidget(tag:).
  • 🌱 Environment-awaredev / staging / prod control verbosity, colors and whether the viewer is even available.

📸 See it in action

Add doc/viewer.png, doc/detail.png and doc/demo.gif to showcase the viewer here.

Searchable log list Rich detail + blame API response diff
doc/viewer.png doc/detail.png doc/diff.png

🚀 Install

dependencies:
  layerx_debugger: ^1.0.1
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.

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 github.com/the-bughex-code/layerx_debugger.

📄 License

BSD-3-Clause © The BugHex. See LICENSE.

Libraries

dio
Opt-in Dio integration for LayerX Debugger.
layerx_debugger
LayerX Debugger — a drop-in debugger and logger for Flutter and GetX.