tracebird_flutter

Tester feedback for Flutter apps, with the technical context already attached.

Your tester shakes the phone, circles what looks wrong, and writes one sentence. You receive a report carrying the console output, the failed requests, the route they walked, the device and the build, all in the same inbox as your web reports.

Future<void> main() async {
  await Tracebird.init(
    const TracebirdOptions(
      projectKey: 'pk_live_…',
      environment: 'staging',
    ),
  );
  runApp(const TracebirdWrapper(child: MyApp()));
}

That is the whole integration. Testers never need an account.

Add every bundle identifier your testers run (com.acme.app, com.acme.beta) to the project's allowlist in Settings. Reports from anywhere else are rejected, which is the mobile equivalent of the web SDK's allowed origins.

What ships with a report

Automatically: console output (last 100 lines), HTTP calls you reported (method, URL, status, duration, never bodies), uncaught framework errors, the navigation trail, the device model, OS, app version and locale.

On demand: the annotated screenshot, and any metadata you attached.

Never: request or response bodies, headers, anything under a TracebirdMask, and anything the tester blurred: those pixels are destroyed on the device before the image is encoded.

Screenshots on screens with maps, WebViews or video

This is the part most SDKs get wrong, so it is worth being precise.

A platform view (a WebView, a Google Map, a camera preview) is not drawn by Flutter. Measured on a real WebView on both platforms:

RepaintBoundary.toImage() native window capture
iOS leaves a hole where the view is captures it correctly
Android captures it correctly came back blank

Android composites platform views into the Flutter scene; iOS overlays them as UIViews. So the SDK uses Flutter's own capture everywhere, and pays for a native capture only on iOS, only on screens where it detects one. A platform that has no native path does not report a failure, because on Android the Flutter capture is the right answer rather than a degraded one. You configure none of it.

One caveat worth knowing: a WebView that has not finished painting is genuinely white, and no capture strategy can invent pixels the platform has not drawn. If a tester reports within a second of a screen appearing, the screenshot shows what was on screen at that moment, blank included.

Content protected by FLAG_SECURE still cannot be captured on Android: that is the platform's decision, and the report is sent without a screenshot.

Hiding sensitive widgets

TracebirdMask(
  child: PatientCard(patient: patient),
)

Masked regions are painted over while the screenshot is still an in-memory buffer, so the real pixels never reach a file, let alone the network.

Triggers

TracebirdOptions(
  projectKey: 'pk_live_…',
  trigger: TracebirdTrigger.shake, // shake · floatingButton · manual
)

shake keeps the UI clean during a usability test. On iOS it uses the system's own shake gesture, on Android an accelerometer detector that requires two jolts in a row so setting the phone down does not file a bug.

With manual, put feedback behind your own menu item:

await Tracebird.report(type: FeedbackType.bug);

Recording network calls

The package ships no HTTP-client dependency, so it works with whatever you use. Report calls from one place:

// Dio
dio.interceptors.add(
  InterceptorsWrapper(
    onResponse: (response, handler) {
      Tracebird.recordHttp(
        method: response.requestOptions.method,
        url: response.requestOptions.uri.toString(),
        status: response.statusCode ?? 0,
        milliseconds: stopwatch.elapsedMilliseconds,
      );
      handler.next(response);
    },
  ),
);

Bodies and headers are deliberately not accepted by recordHttp.

MaterialApp(
  navigatorObservers: [TracebirdNavigatorObserver()],
)

The routes the tester walked become the first draft of your repro steps.

Offline testers

Reports written in a lift are persisted, retried with a widening backoff, and resolved by an idempotency key, so a flaky connection never files the same shake of the phone twice. Tracebird.pendingReports tells you how many are waiting; Tracebird.flush() retries now.

Dependencies

None at runtime, on purpose. Device details, the cache directory, shake detection, screen capture and JPEG encoding all go through this plugin's own platform channel rather than pulling in four packages your app would then have to keep in version lockstep.

Requirements

Flutter 3.3+, iOS 13+, Android API 21+.

Full documentation: tracebird.dev/docs/flutter

License

MIT © Tracebird

Libraries

tracebird_flutter
Tester feedback for Flutter apps.