Skip to main content

flog_flow 0.7.0 copy "flog_flow: ^0.7.0" to clipboard
flog_flow: ^0.7.0 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: …)`. Keep `CaptureConfig.offlineMode` off unless the team asks for full headers and bodies.

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.

Migrating from 0.6.x #

Version 0.7.0 focuses on local session recording and self-contained HTML reports. Cloud logging and upload APIs from 0.6.x are no longer included. Follow the quick start below to record and export locally. For Dio capture, install flog_flow_dio and import FlogDioInterceptor from that package.

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

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 small .zip (about 5K tokens instead of 100K for the HTML) ready to paste into 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.

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.

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.

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.

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

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 only with offlineMode, which is off by default.
  • Need a protected file? exportFlowx(passphrase: …) writes an AES-256 encrypted .flowx that opens from the Abrir button of any report.

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

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`
};

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.

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

11
likes
0
points
428
downloads

Publisher

verified publisheralecodeando.dev

Weekly Downloads

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.

Topics

#debugging #session-replay #testing #network

License

unknown (license)

Dependencies

archive, cryptography, flutter, image, path, path_provider

More

Packages that depend on flog_flow