Use this page when you need the exact Flutter SDK behavior and option surface.
For setup instructions, start with the Flutter SDK guide.
Support matrix
| Area | Flutter SDK behavior |
|---|
| Package | lucent_flutter |
| Supported platforms | iOS and Android |
| Deferred platforms | Flutter Web and macOS are not in the MVP |
| Replay mode | Screenshot-mode mobile replay |
| Backend classification | lucent-flutter maps to mobile_flutter |
| iOS packaging | CocoaPods is verified |
| Android packaging | Flutter plugin with Android min SDK 23; release apps need android.permission.INTERNET |
| Example app | sdk-flutter-lucent/example in the SDK repo |
Replay envelope
Flutter replay is always sent as mobile screenshot replay:
The SDK captures a masked RepaintBoundary image from LucentWidget, encodes
it as a mobile screenshot wireframe, and posts batches to /api/sdk/replay.
The backend classifies these sessions as mobile_flutter.
Persistence and retry
Replay batches are persisted on disk before upload on iOS and Android. Failed
uploads remain queued for retry.
| Limit | Value |
|---|
| Maximum queued batch files | 100 |
| Maximum queued bytes | 50 MB |
| Maximum queued age | 7 days |
Android also persists current in-memory replay events between flushes, so
active events can survive a process restart before they become an upload batch.
flush() is best effort. If the network request fails, the persisted batch
stays queued and is retried later.
When LucentWidget sees the app pause or become inactive, it emits
Application Backgrounded and requests a flush. When the app resumes, it emits
Application Became Active.
Configuration
Pass LucentConfig to Lucent().setup(...).
Core
| Option | Default | Notes |
|---|
publicKey | Required | Your Lucent public key. Safe to expose in client code. |
ingestBaseUrl | https://ingest-api.lucenthq.com | Override only for self-hosted or internal testing. |
environment | LucentEnvironment.production | Use staging or development for non-production apps. |
autoStart | true | Set to false when you need to wait for consent. |
remoteConfig | true | Fetches /api/sdk/init before auto-starting when enabled. |
sessionReplaySampleRate | 1 | Clamped between 0 and 1. |
When remoteConfig is enabled, native replay waits for /api/sdk/init before
auto-starting. If the request fails, the SDK falls back to local config.
Remote config can disable recording, override session sample rate, update
batching limits, change minimumDuration, toggle log/network telemetry, and
replace eventTriggers.
Privacy
| Option | Default | Notes |
|---|
privacy.maskAllText | true | Masks Text, RichText, and editable text widgets by default. |
privacy.maskAllImages | true | Masks rendered image widgets by default. |
LucentMask forces masking for a subtree. LucentNoMask makes its subtree
visible, so keep sensitive widgets outside any LucentNoMask region.
Flutter does not have browser-style CSS selectors. Review any
LucentNoMask subtree carefully before shipping.
Capture
| Option | Default | Captures |
|---|
capture.touches | true | Pointer-down coordinates and $touch events. |
capture.appLifecycle | true | Application Opened, Application Became Active, and Application Backgrounded. |
capture.errors | true | Flutter framework errors. |
capture.unhandledRejections | true | PlatformDispatcher async errors. |
capture.nativeCrashes | true | Native uncaught exceptions on next launch. |
capture.logs | true | debugPrint, print inside runZoned, and manual logs. |
capture.network | true | dart:io network breadcrumbs inside runZoned and manual network breadcrumbs. |
Batching
| Option | Default | Notes |
|---|
batching.flushInterval | 10 seconds | Native flush loop interval. |
batching.maxEventsPerBatch | 500 | Reaching this triggers a flush. |
batching.maxBatchBytes | 1048576 | Reaching this triggers a flush. |
Native code clamps flush interval, event count, and byte limits to bounded
ranges so misconfiguration does not create extremely small or huge batches.
Replay
| Option | Default | Notes |
|---|
replay.minimumDuration | Duration.zero | Final flushes shorter than this are dropped. |
replay.screenshotThrottle | 1 second | How often LucentWidget attempts a screenshot. |
replay.screenshotBackgroundCapture | false | Screenshots stop while the app is backgrounded by default. |
replay.eventTriggers | [] | When set, recording starts after one of these events is tracked. |
snapshotMode is not configurable. It is always screenshot.
| Option | Event property |
|---|
metadata.app | $app_name |
metadata.namespace | $app_namespace |
metadata.version | $app_version |
metadata.build | $app_build |
metadata.release | release |
metadata.properties | Merged into event metadata |
Use Lucent().setSessionProperties(...) and Lucent().setUserProperties(...)
when values change during a session.
The native payload also includes common mobile context: userAgent, language,
snapshotSource, snapshotLibrary, snapshotMode, os, deviceType, and
environment.
Public API
| Method | Use for |
|---|
Lucent().setup(config) | Initialize the SDK and native modules. |
Lucent().stop() | Stop native recording, uninstall Dart hooks, and perform a final native flush. |
Lucent().track(name, properties?) | Capture a custom event. |
Lucent().identify(user) | Associate future events with a known user. |
Lucent().resetIdentity() | Clear the known user and generate a new anonymous ID. |
Lucent().screen(name, properties?) | Emit $screen and update replay screen context. |
Lucent().setSessionProperties(properties) | Attach session-level properties. |
Lucent().setUserProperties(properties) | Attach user-level properties. |
Lucent().captureLog(message, level, properties) | Capture a manual log breadcrumb. |
Lucent().captureNetwork(...) | Capture a manual network breadcrumb. |
Lucent().captureNetworkRequest(...) | Wrap an async request and capture timing/status. |
Lucent().captureException(error, stackTrace?, properties?) | Capture a handled exception. |
Lucent().addExceptionStep(message, properties?) | Add an exception breadcrumb. |
Lucent().startSessionRecording(resumeCurrent: true) | Resume screenshot replay. |
Lucent().stopSessionRecording() | Pause screenshot replay and final flush. |
Lucent().isSessionReplayActive() | Check whether replay is currently active. |
Lucent().flush(finalFlush: false) | Flush queued events. Set finalFlush: true for a final flush. |
Lucent().getSessionInfo() | Read sessionId, windowId, and recording state. |
Lucent().runZoned(body, onError?) | Capture print, async errors, and dart:io network inside a zone. |
getSessionInfo() returns sessionId, windowId, and recordingEnabled.
Use runZoned(..., onError: handler) if your app already has its own
top-level async error handler.
Navigation reference
LucentObserver accepts two optional callbacks:
| Callback | Use for |
|---|
nameExtractor(RouteSettings settings) | Convert route settings into a screen name. Defaults to settings.name. |
routeFilter(Route<dynamic>? route) | Decide which routes should emit screens. Defaults to route is PageRoute. |
For routers that do not expose named PageRoutes, call Lucent().screen(...)
from your router listener instead.
Network privacy
Automatic network capture is limited to dart:io HttpClient traffic created
inside Lucent().runZoned(...). Other HTTP clients need a wrapper or
interceptor that calls captureNetworkRequest or captureNetwork.
Lucent captures:
| Field | Captured |
|---|
| HTTP method | Yes |
| URL origin and path | Yes |
| URL user info, query, and fragment | No |
| Status code | Yes, when available |
| Duration | Yes, when available |
| Transfer size | Yes, when available |
| Request headers | No |
| Response headers | No |
| Request body | No |
| Response body | No |
Because headers and bodies are not captured, there are no per-field masking
rules for them. Sanitize URLs before manual capture if a path segment contains
sensitive data.
Error and crash behavior
| Error source | Behavior |
|---|
| Flutter framework errors | Captured through FlutterError.onError when capture.errors is enabled. |
| Async platform errors | Captured through PlatformDispatcher.instance.onError when capture.unhandledRejections is enabled. |
| Handled exceptions | Capture manually with Lucent().captureException(...). |
| Native uncaught exceptions | Stored synchronously and emitted as $native_crash on the next launch. |
Native crash delivery is best effort. If the process cannot write the pending
crash payload before termination, there may be no next-launch crash event.
Parity checklist
| Capability | Flutter SDK | Notes |
|---|
| iOS replay | Supported | Screenshot-mode mobile replay. |
| Android replay | Supported | Screenshot-mode mobile replay. |
| Flutter Web replay | Deferred | No web/canvas capture path in the MVP. |
| Root replay wrapper | Supported | Use LucentWidget. |
| Route tracking | Supported | Use LucentObserver or Lucent().screen(...). |
| Custom events | Supported | Use Lucent().track(...). |
| Identify/reset | Supported | Use LucentUser and resetIdentity. |
| Touch capture | Supported | Pointer-down coordinate events. |
| Privacy defaults | Supported | Text, inputs, and images mask by default. |
| Logs | Supported | debugPrint, print in runZoned, and manual logs. |
| Network breadcrumbs | Supported | dart:io in runZoned plus manual APIs. |
| Handled errors | Supported | captureException and exception steps. |
| Native crashes | Supported | Next-launch $native_crash, best effort. |
| Persistent replay queue | Supported | iOS and Android batch queues with bounded retention. |
| Android current-event persistence | Supported | Current events persist between flushes. |
| Remote config | Supported | /api/sdk/init when enabled. |
| Event-triggered replay | Supported | LucentReplayConfig.eventTriggers. |
| CocoaPods | Supported | Verified iOS packaging path. |
| Swift Package Manager | Deferred | Use CocoaPods for the MVP. |
Not in the Flutter MVP
These browser or React Native capabilities are not available in
lucent_flutter 0.1.0:
| Capability | Status |
|---|
| Flutter Web replay | Deferred until a web/canvas capture path exists. |
| CSS selector privacy rules | Not applicable to Flutter widget trees. Use LucentMask and LucentNoMask. |
networkIgnoreUrls | Not exposed. Sanitize URLs manually before manual capture if needed. |
| Console level filtering | Not exposed. debugPrint, zoned print, and manual logs use the configured capture toggle. |
| Rage-click and dead-click detection | Browser-only today. Flutter touch capture is coordinate-level. |
| Alias/group APIs | Not in the Flutter MVP. |
| Persistent super-property registry | Not in the Flutter MVP. Use metadata, session properties, and user properties. |
| Source maps or native symbolication | External to the Flutter SDK docs today. |
| Expo/Metro tooling | React Native only. |
Troubleshooting
| Symptom | Check |
|---|
| No replay appears | Confirm LucentWidget wraps the app root and sessionReplaySampleRate is greater than 0. |
| No screen names | Add named routes, customize LucentObserver, or call Lucent().screen(...) manually. |
print logs are missing | Make sure runApp runs inside Lucent().runZoned(...). |
| Network calls are missing | Confirm the client uses dart:io inside runZoned, or add manual/interceptor capture. |
| iOS build fails around pods | Run flutter pub get, then cd ios && pod install. |
| iOS SPM warnings appear | Use CocoaPods for the MVP; Swift Package Manager support is deferred. |
| A physical iOS device will not run | Enable Developer Mode and trust the development certificate/device pairing. |
| Android release uploads fail | Add <uses-permission android:name="android.permission.INTERNET" /> to android/app/src/main/AndroidManifest.xml. |
| Android device install fails | Confirm USB debugging is enabled and the device trusts the development machine. |
| Replay is too sparse | Lower replay.screenshotThrottle, but watch bandwidth and battery use. |