Skip to content
Docs menu · Flutter

Install — Mobile

Install AnyReplay in a Flutter App

Record Flutter app sessions with anyreplay_flutter: the screen read as words and boxes, never screenshots, so every replay can be read and translated.

The Flutter SDK records your app the way the React Native SDK does: as a tree of boxes and words read from Flutter itself, never as screenshots. The element tree says what each thing is — a switch that is on, a field that holds a password, the name of an icon — and the render tree says where it is on screen. Every word reaches the replay as text, so it can be searched, masked word by word and translated.

Requirements

  • Flutter 3.27 or later.
  • Android and iOS. No native code of its own: it uses shared_preferences, package_info_plus and device_info_plus.

Tested with Flutter 3.47 on the iOS simulator. Android is supported and not yet tested on a device; tell us through the contact page if anything is off there.

Install

flutter pub add anyreplay_flutter

The package brings plugins with native code, so after adding it stop the app and start it again with flutter run: a hot reload or hot restart does not load them.

Start recording

import 'package:anyreplay_flutter/anyreplay_flutter.dart';
import 'package:flutter/material.dart';

// The app id (bundle id / package name) is read from the app itself.
void main() {
  AnyReplay.init(const AnyReplayOptions(
    projectKey: 'YOUR_PROJECT_KEY',
    ingestUrl: 'https://in.anyreplay.com',
    // Stop recording after this many sessions in a calendar month.
    // maxSessionsPerMonth: 10000,
    // Fields are masked: what people type replays as asterisks. Secure
    // fields, passwords, one-time codes and card fields are masked either
    // way. Uncomment the first line to record what people type, or the
    // second to mask every field in a way nothing can weaken.
    // maskAllInputs: false,
    // maskAllTyping: true
  ));
  runApp(MaterialApp(
    navigatorObservers: [AnyReplayNavigatorObserver()],
    home: const HomeScreen(),
  ));
}
lib/main.dart

AnyReplay.init reads the app id, version and device first, then starts recording. Every other call — consent, identify, track, trackError, screen, flush, stop — may be made before it has finished and is applied the moment the recorder exists. A second init is ignored. Nothing ever throws into your app.

AnyReplay.consent(true);                    // with requireConsent: true
AnyReplay.identify(userId: 'u_1842');
AnyReplay.track('checkout_started', {'cart': 3});

try {
  await pay();
} catch (error, stack) {
  AnyReplay.trackError(error, stack);
}

Screens

AnyReplayNavigatorObserver names each screen after its route (RouteSettings.name), with any query string cut off. Add it to MaterialApp.navigatorObservers, or to GoRouter(observers: […]) with go_router. Dialogs, menus and sheets are layers of the screen they open over, not screens. Name routes after what they are (/orders/:id), never after who is looking.

What is masked

As in every AnyReplay SDK, what people type into a text field is masked by default. maskAllInputs: false records it, and some fields never are recorded even then: obscureText fields, fields whose autofillHints name a password, a one-time code or a card, the visible-password keyboard, fields whose hint, label or key names one of those, and any value shaped like a card number. To hide more, set maskImages: true, or wrap a widget in AnyReplayMask — everything inside it is masked. See masking.

AnyReplayMask(child: Text(order.deliveryAddress))

What a replay shows

In your appIn the replay
Text, fields, buttons, switches, checkboxes, sliders, pickersThe same, with their words and state
IconsThe icon, by name
Network images, bundled imagesThe image: by address, or uploaded once per project by content hash
Lists and scroll viewsScrolled as the person scrolled them
Dialogs, menus, bottom sheets, the keyboardLayers over the screen; the keyboard as a grey block
Your own CustomPainter, web views, maps, videosA labelled placeholder, with its Semantics label

Allowed apps

An app has no web origin, so the SDK sends the app's bundle id (iOS) or package name (Android) as appId, read from the app. If the project lists allowed domains or apps, add that id under Allowed apps; pass appId to AnyReplayOptions only to override it.

Errors and the app lifecycle

Uncaught errors are recorded through FlutterError.onError and PlatformDispatcher.onError while a recording runs; the handlers that were there before — your crash reporter, Flutter's console output — are always called, and put back on stop(). Set recordErrors: false to leave both alone. When the app goes to the background the recorder sends what it holds at once and stops reading the screen until it returns; after 30 idle minutes the next session starts.

Store requirements

  • App Store: the SDK uses no required-reason API of its own; shared_preferences declares its use of UserDefaults. In your privacy answers, AnyReplay collects product interaction, other user content (the words on screen), crash and diagnostic data and a device id, not linked and not used for tracking — plus user id or e-mail, linked, if you call identify.
  • Google Play: collected, not shared, encrypted in transit. Exclude the shared_preferences file from Auto Backup so a restored phone is a new visitor; the README has the rule.

Known limits

  • What an app paints itself (a CustomPainter, a chart library, a game) is a placeholder. Wrap it in Semantics(label: …) to give it a name in the replay.
  • Web views are recorded as a placeholder; their pages are not.
  • An --obfuscate build cannot recognise web views, maps and video players by name; they become plain placeholders.
  • Unsent events are kept in memory while the device is offline; if the app is killed before it reconnects they are lost.