flog_flow 0.7.2
flog_flow: ^0.7.2 copied to clipboard
Records the user journey in a Flutter app (screens, taps, network, logs, errors) and exports it as one self-contained HTML file to replay in any browser.
flog_flow #
Record what a user does in your Flutter app and get one self-contained
.html file that replays the whole session in any browser: screens, taps,
network requests, errors and logs on a single timeline. Attach it to a ticket,
send it over Slack, open it with a double click. No server, no account, no
internet.
🤖 Install with an AI assistant #
Paste this prompt into your coding assistant (Claude Code, Cursor, Copilot, Gemini…) from the root of your Flutter project:
Add the Flutter package flog_flow to this project so test sessions can be recorded and exported as a self-contained HTML report. Adapt every step to the existing code: keep the current architecture, chain existing handlers instead of replacing them, and don't change app behavior in production.
1. Dependencies: run `flutter pub add flog_flow`. If the project uses Dio, also run `flutter pub add flog_flow_dio`. If it uses package:http (directly or through another package), also run `flutter pub add flog_flow_http`.
2. Root widget (MaterialApp, MaterialApp.router, CupertinoApp, GetMaterialApp…):
- Wrap the app with `FlogFlogScope` through `builder: (context, child) => FlogFlogScope(child: child!)`. If a `builder` already exists, wrap what it returns instead of replacing it.
- Register `FlogFlow.instance.navigatorObserver` in `navigatorObservers` (with go_router: `GoRouter(observers: [...])`). For every additional Navigator (ShellRoute, nested navigators, tabs with their own Navigator) use `FlogFlow.instance.newNavigatorObserver()`; never share one observer instance between two navigators.
3. Network: add `FlogDioInterceptor()` to the interceptors of every Dio instance (`import 'package:flog_flow_dio/flog_flow_dio.dart'`). For package:http, use `FlogHttpClient()` wherever an `http.Client` is created, or wrap the app in main with `http.runWithClient(() => runApp(...), () => FlogHttpClient())` (`import 'package:flog_flow_http/flog_flow_http.dart'`).
4. Errors: in main(), after `WidgetsFlutterBinding.ensureInitialized()`, chain the global handlers so uncaught errors are also recorded, keeping any existing crash reporter (Crashlytics, Sentry…) working:
- `FlutterError.onError`: call the previous handler, then `FlogFlow.instance.recordError(details.exception, stackTrace: details.stack, context: 'FlutterError')`.
- `PlatformDispatcher.instance.onError`: call `FlogFlow.instance.recordError(error, stackTrace: stack)` and return the previous handler's result (or false).
Inside existing try/catch blocks that show an error to the user, add `FlogFlow.instance.recordError(e, stackTrace: st, context: '<where>')`.
5. Start recording in main(), before runApp, only in non-production builds (use the project's flavor or environment flag if it has one, otherwise `!kReleaseMode`):
`await FlogFlow.instance.start(appName: '<app name>', appVersion: '<version if available>');`
If the app has authentication, call `FlogFlow.instance.setUser('<user id>')` after login and `FlogFlow.instance.setUser(null)` on logout.
6. Export: add a discreet way to export the session, available only in non-production builds (an item in an existing debug or settings menu; otherwise a small floating button shown only when recording). It must run `final file = await FlogFlow.instance.export();` and share the file with share_plus if the project already uses it; otherwise show the file path in a dialog and print it.
7. Privacy: password fields are masked in screenshots automatically. Wrap any other sensitive widget (card numbers, balances, personal data) in `RedactedZone(child: …)`. `CaptureConfig.offlineMode` is on by default and records full headers and bodies. Set `offlineMode: false` if the team does not need this detail.
8. Run `flutter pub get` and `flutter analyze`, fix anything you introduced, and finish with a short summary: files changed and how to export a session.
Quick start #
flutter pub add flog_flow
flutter pub add flog_flow_dio # if you use Dio
flutter pub add flog_flow_http # if you use package:http
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!kReleaseMode) await FlogFlow.instance.start(appName: 'MyApp');
runApp(const MyApp());
}
MaterialApp(
navigatorObservers: [FlogFlow.instance.navigatorObserver],
builder: (context, child) => FlogFlogScope(child: child!),
// ...
);
// When the tester is done (e.g. from a debug menu):
final file = await FlogFlow.instance.export(); // flow_<session>.html
Features #
One HTML file, nothing else #
The whole session (timeline, screenshots and viewer) lives in a single .html
that opens offline in Chrome, Safari, Edge or Firefox. Anyone can open it: no
SDK, no dashboard, no login. Data never leaves the device unless someone
shares the file.

Step-by-step replay #
Every screen and every interaction, in order: the exact point of each tap, the direction of each scroll, the navigation it triggered and what the screen looked like at that moment. Play it like a video or move with the arrow keys, and copy any screenshot (with the tap marker) in one click.

Network traffic #
Each request appears on the timeline next to the tap that caused it: method,
URL, status and duration, plus headers and bodies with a JSON viewer.
Copiar cURL (copy as cURL) repeats the exact request from a terminal. Works with Dio
(flog_flow_dio), package:http (flog_flow_http) or any client through
FlogFlow.instance.recordHttpCall.
dio.interceptors.add(FlogDioInterceptor()); // Dio
http.runWithClient(() => runApp(const MyApp()), () => FlogHttpClient()); // package:http

This request is from the recorded session. Its authorization header was redacted before generating documentation media.
Errors with context #
Every error is stored with its stack trace and a screenshot of the screen it happened on, in the same timeline as the taps and requests that led to it. Connect the global handlers once and nothing escapes.
final onFlutterError = FlutterError.onError;
FlutterError.onError = (details) {
FlogFlow.instance.recordError(details.exception, stackTrace: details.stack);
onFlutterError?.call(details);
};
final onPlatformError = PlatformDispatcher.instance.onError;
PlatformDispatcher.instance.onError = (error, stack) {
FlogFlow.instance.recordError(error, stackTrace: stack);
return onPlatformError?.call(error, stack) ?? false;
};

Export for AI #
One click builds a configurable .zip for analysis in Claude, ChatGPT or
Gemini: a readable session.md, a
session.json and the key screenshots. It starts with automatic findings:
failed, slow or repeated requests, identical responses, unlabeled taps, frozen
UI and connection drops. Secrets (tokens, cards, passwords) are hidden by
default.

Read the generated session summary.
Export to video #
Turns the session (or a selected range) into an MP4, rendered in the browser: animated taps and scrolls, screen transitions, and a side panel with the requests and notes as they happen. Horizontal 16:9 or vertical 9:16. Evidence for the ticket without having recorded the screen.
Watch or download the generated MP4 — 97 seconds, 1280 × 720, generated by the viewer from this session.

Real IMDUMB session on iOS: 16 screens, 39 interactions, 31 requests and two recorded errors. Documentation copies have authorization headers removed. The reviewer note shown below was added for this demonstration.
Steps to reproduce #
Select a range of the timeline and get the bug report written: environment, precondition, numbered steps (in Spanish, like the viewer: "Toca «Pagar» → se abre «Checkout»"), observed result (errors, failed requests) and expected result. Markdown for Jira, GitHub or Slack, editable before copying.

Read the generated reproduction steps.
Notes on any action #
Testers write a note on the exact action where something went wrong. Notes
show up in the video, the steps to reproduce and the AI export, and
Descargar copia con notas (download copy with notes) saves them inside the
same .html for the next person.

The note is a demonstration annotation added to the recorded error.
Search everything #
⌘K / Ctrl+K searches URLs, request and response bodies, headers, logs,
errors and notes. Opening a result jumps to that action and highlights every
match.

Flow map #
All the screens visited as a map, with the transitions between them and the action that triggered each one. The fastest way to see the path a user took.

System signals #
The timeline also shows what the app went through, not only what the user tapped:
- app sent to background and back (and for how long);
- connection changes: wifi, mobile data, none;
- keyboard opened and closed;
- which text fields were edited (never what was typed);
- slow frames and UI freezes (profile/release builds).

This session includes a 26-second background interval and field-edit events. It contains no connectivity changes, keyboard events or slow-frame events.
Private by default #
- Password fields are masked in screenshots automatically; mask every text
field with
CaptureConfig(maskTextFields: true). - Wrap anything else sensitive in
RedactedZone. - Nothing stays on disk between sessions.
- Full headers and bodies are enabled by default with
offlineMode: true. Set it tofalseto omit headers and use body previews. Raw HTML exports can contain authorization tokens, cookies and personal data. - Need a protected file?
exportFlowx(passphrase: …)writes an AES-256 encrypted.flowxthat opens from the Abrir button of any report.

The name field is masked in the original capture. This session does not contain a password field or an encrypted-file export.
Logs and breadcrumbs #
Capture debugPrint output (and print, through FlogLogCapture.zoneSpec();
opt-in), add your own breadcrumbs, and label the action that triggers a
navigation so the flow reads like a story.
FlogLogCapture.installDebugPrintCapture(); // with CaptureConfig(captureLogs: true)
FlogFlow.instance.breadcrumb('Cart updated', data: {'items': 3});
FlogFlow.instance.tag('User clicks "Pay"'); // right before Navigator.push

These two recorded breadcrumbs identify edited fields without their values. This session contains no captured print or debugPrint logs.
Keep only the sessions that fail #
Record always but keep the flow only when something breaks, and export it the moment the error happens.
await FlogFlow.instance.start(
appName: 'MyApp',
config: const CaptureConfig(recordOnErrorOnly: true),
);
FlogFlow.instance.onErrorRecorded = () async {
final file = await FlogFlow.instance.exportIfError();
// upload or share `file`
};

The session illustrates recorded errors; the retention policy is configured in the SDK with the code above.
Light on the app #
Screenshots are taken outside animations and encoded during idle time, kept on
disk instead of in memory; exports run in an isolate, and the expensive
options are opt-in. Everything is tunable in CaptureConfig.
For a lighter recording, reduce screenshot size and disable body capture:
const CaptureConfig(
offlineMode: false,
captureNetworkBodies: false,
framePerInteraction: false,
pixelRatio: 0.75,
jpegQuality: 55,
);
The media above demonstrate the exported session; they do not measure recording overhead.
Configuration #
FlogFlow.instance.start(appName: …, config: CaptureConfig(…)). The most used
options:
| Option | Default | What it does |
|---|---|---|
captureNetwork |
true |
Records requests from a network adapter |
captureNetworkBodies |
true |
Adds the full URL and body previews |
offlineMode |
true |
Full bodies and headers (enables Copiar cURL); sensitive |
captureLogs |
false |
Records debugPrint/print output |
framePerInteraction |
true |
One screenshot per tap, frozen at the moment of the tap |
maskPasswordFields |
true |
Masks password fields in screenshots |
maskTextFields |
false |
Masks every text field |
recordOnErrorOnly |
false |
Keeps the session only if an error happens |
recordAppLifecycle, recordConnectivity, recordKeyboard, recordFieldEdits, recordSlowFrames |
true |
System signals on the timeline |
pixelRatio / jpegQuality |
1.0 / 65 |
Screenshot sharpness and size |
Companion packages #
| Package | Use it when |
|---|---|
flog_flow_dio |
Your app uses Dio |
flog_flow_http |
Your app uses package:http (or a package built on it) |
Platforms #
Android, iOS, macOS, Windows and Linux (Flutter web is not supported: the
recorder uses dart:io). The viewer inside the HTML is in Spanish.
