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.
Navigation breadcrumbs
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.