← Platforms

Flutter

Remote debugging for Flutter on real devices — widget-tree traversal, gesture injection, screenshots, logs and hot-reload, driven over MCP by your AI coding tool.

The Flutter client is a pure Dart package: no platform channels, no Gradle or CocoaPods edits. Add it as a git dependency, bootstrap it in main(), and your running app connects to OmniDebugLink so an AI tool can inspect and drive it on a phone, an emulator, or a colleague's device. It also brings the one capability no other platform has: hot_reload, where the AI edits your Dart code, reloads the running app, and keeps testing.

Core features

Pure Dart, zero native configuration

No platform channels, no per-platform setup. Dependencies are just web_socket_channel, shared_preferences and vm_service, and task handlers run on your main isolate — one language, one place to debug.

Resilient widget addressing

The element tree rebuilds constantly and runs 70+ levels deep, so tasks locate targets by key / text / widget_type substring (+index) and complete locate-and-act atomically in one task. Path is only an exact-address fallback.

Flutter-exclusive hot_reload

In debug or profile builds with a reachable VM Service, hot_reload reloads sources in place — the AI edit → reload → verify loop stays inside one session.

Read-only mode built in

OmniDebugLink.actionsEnabled (default true) gates every write operation. Set it false and the SDK becomes a read-only observer, announcing that mode with its hello.

Requirements

Install

dependencies:
  omnidebuglink:
    git:
      url: https://github.com/omnidebuglink/omnidebuglink_flutter.git
      ref: v0.2.0

Wire it up

1

Bootstrap in main()

bootstrap() takes over the zone, catching uncaught async errors and print output for the log stream.

import 'package:omnidebuglink/omnidebuglink.dart';

void main() {
  OmniDebugLink.bootstrap(
    token: '<clientToken>',
    appVersion: '1.2.0',
    app: const MyApp(),
  );
}
2

Or manage the lifecycle yourself

If your app owns its own zone, start the link after runApp and forward errors with recordLog / recordError. The relay URL is baked in.

runApp(const MyApp());
await OmniDebugLink.start('<clientToken>');
3

Optional: report the route stack

This lets get_state return the navigation stack; without it you get routes: null plus guidance instead.

MaterialApp(navigatorObservers: [OmniDebugLink.routeObserver], ...)
4

Run and connect

Launch on a device: it connects, announces its capabilities, and shows up in your account for synchronous MCP calls.

Built-in tasks

19 built-in tasks, plus conditional ones such as hot_reload. Registry changes re-announce automatically, so custom tasks are discovered without any server-side change.

Read tasks

TaskWhat it does
ui_traverseWidget tree snapshot — a flat list by default (depth/name/key/text/rect/center, token-efficient); flat:false for nested output, 3000-node cap.
find_objectsSearch by text / key / widget_type substring; matches carry center coords and a hint to pass the same locator to an action task.
view_componentOne widget in depth: targeted properties plus renderObject info, same locators.
wait_forPolls every 200 ms until a key / text / widget_type / path appears; timeout returns found: false, not an error.
screenshotPNG via OffsetLayer.toImage, downsampling loop to fit the base64 budget.
read_logsSubscription buffer (no history before start): Flutter framework errors, platform-dispatched errors, debugPrint.
get_perfFrameTiming percentiles (p50/p95/p99) plus RSS.
get_stateApp state plus the route stack (register OmniDebugLink.routeObserver).
prefsRead SharedPreferences (get / list).

Write tasks

All gated by actionsEnabled.

TaskWhat it does
ui_clickReal gesture tap (GestureBinding.handlePointerEvent) on a target located by key/text/widget_type/index/path — atomically in one call.
tap_screenTap at normalized 0-1 coordinates (top-left origin).
swipeDrag gesture with per-frame delta and increasing timestamps (velocity tracking needs both).
long_pressPointer down, hold until the recognizer's own timer fires, then up.
input_textWrite text into a field located by key/widget_type/path or the current focus. Note: text is the value to enter, not a locator.
set_componentTargeted mutations only (no reflection in AOT): text / scroll_offset / scroll_to_end / scroll_to_start / checked.
send_keySoft-dispatched enter / escape / tab / space.
prefsWrite / delete SharedPreferences with valueType coercion; the result echoes the stored type.

Flutter-only

hot_reload reloads sources in place — a natural fit for an AI edit → reload → verify loop. There is no hot_restart: that RPC requires a flutter run session, which standalone apps never have. Basics round out the set: echo, ping, get_stats.

Coordinates and screenshots

Normalized coordinates are 0-1 with a top-left origin — same as Android (Unity is bottom-left). From a screenshot pixel: x=(px+0.5)/W, y=(py+0.5)/H, no flipping. All rect/center values are logical pixels, and screenshots are PNG.

What read_logs captures

SourceCaptured?
Flutter framework errors (build/layout)yes
Uncaught platform-dispatched errorsyes
debugPrintyes, while the link is started
Uncaught async errors / printonly inside the bootstrap() zone
Native logs (logcat / nslog)no
Anything logged before startno — no history

Extend it with custom tasks

Register your own task with a type, handler, description and payload schema; capability changes are announced automatically.

OmniDebugLink.tasks.register(
  type,
  handler,
  description: '...',
  payloadSchema: {...},
);

Good to know