Logopicto

Configuration Reference

Every constructor parameter and --dart-define the recording SDK reads.

Picto#

The public embedding facade — see Quick Start for the usual flow. Everything below is a static method; there's nothing to construct.

Picto.setup#

ParameterRequiredMeaning
apiKeyYesThe client app's API key — from the dashboard.
appVersion Yes Your app's version string, stored on every upload from here on.
pendingUploadsDir No Where a failed-after-all-retries upload gets queued for a later retry. Defaults to picto_pending_uploads/ under the system temp directory.
maxRecordingDuration No How long a single session keeps accepting events before it stops recording. Applies to every session of the app run, not just the first. Defaults to 10 minutes — see the duration cap for what raising it costs.
errorWindowBefore No How much of a session before its first uncaught error the upload keeps. Applies to every session of the app run, not just the first. Defaults to 30 seconds, and the real lookback lands somewhere in [errorWindowBefore, errorWindowBefore + 5s] . It keeps bytes that were already captured and never causes any to be captured, so a recording an error started has no lookback at all whatever you set here — see the error capture window .
errorWindowAfter No The ceiling on how much of a session after its first uncaught error the upload keeps, after which the session ends and uploads. A ceiling, not a countdown: the app being backgrounded already ends the session and usually gets there first. Applies to every session of the app run. Defaults to 30 seconds — see the error capture window .
backgroundTimeout No How long the app may sit in the background and still come back to the same session. Applies to every backgrounding of the app run. Defaults to 30 seconds. Not a delay before uploading — backgrounding still ends and uploads the recording immediately; this governs only whether the next one continues that session_id at the next sequence or starts a new one. Negative is clamped to zero, which means every resume starts a new session. See Sessions & Segments .

There is no endpoint parameter: uploads go to https://api.picto.dev, which is compiled into the SDK.

Call once, before runApp(). Does not start recording — see Starting & Stopping.

Picto.startRecording()#

Turns recording on. The only way a session begins deliberately — an uncaught error also starts one (Errors That Start Recording), and foregrounding the app resumes one a backgrounding cut (Sessions & Segments). A no-op if already recording, and a no-op on a run that sampling declined.

"A no-op if already recording" is literal: a call made during a live session does not start a second recording, it does nothing. A session has to have ended first — see Starting & Stopping, and restartRecording for ending one and starting the next in a single call.

Picto.restartRecording({metadata})#

Ends the current recording, uploads it, and starts a genuinely new session — its own session_id, its own clock, its own embedded content, its own hadError. Throws if setup was never called. metadata is passed to the ending session's upload and does not attach to the new one.

Returns the PictoSessionEndOutcome of the session that ended, or null.

Return valueMeaning
uploaded / uploadFailed / recordingPipelineAbsent What became of the old recording. A new session is running.
nothingRecording Nothing was recording, so nothing was uploaded. A new session is running — you do not need to check isRecording first.
null This run was not sampled. Nothing ended and nothing started.

The old session's upload is awaited before the new recorder is installed, and any error-capture window armed by the old session is cancelled so it cannot end the new one early. Picto.setTag values do not carry across the restart; the user named by Picto.identify does. See Starting & Stopping.

Picto.isRecording#

Mirrors whether a recording is currently active.

Picto.endSessionAndUpload({metadata})#

Ends the current recording and uploads it — the manual-override path (backgrounding/terminating the app is the automatic one, see Starting & Stopping). Throws if setup was never called. metadata is optional free-form tags merged into the upload — see Tags.

Returns true for exactly one thing: the recording reached the backend. false covers every other case, including ones where nothing went wrong at all — see endSessionAndUploadOutcome below before logging false as a failure.

Picto.endSessionAndUploadOutcome({metadata})#

The same call, reporting which of the four things happened instead of collapsing them into a bool. Use this when your app logs or reacts to the result.

OutcomeWhat happened
uploaded The recording reached the backend. The only outcome endSessionAndUpload reports as true .
uploadFailed The upload was attempted and did not land. The recording is queued on disk , and a later retryPendingUploads — including the ones the SDK runs itself — will re-send it. Nothing is lost yet.
nothingRecording No session was active, so there was nothing to end. No request, no file, nothing queued.
recordingPipelineAbsent The recording pipeline was never installed, so the session was empty by construction. The session is still ended. This is the outcome in every widget and integration_test suite, by design — see Testing .

nothingRecording is the one worth knowing about, because it is not an error and usually not even a mistake. A session ends on its own when the app is backgrounded, and the error capture window ends one roughly 30 seconds after an uncaught error. An app that ends sessions from a button meets this whenever one of those got there first:

switch (await Picto.endSessionAndUploadOutcome()) {
  case PictoSessionEndOutcome.uploaded:
    debugPrint('session uploaded');
  case PictoSessionEndOutcome.uploadFailed:
    debugPrint('upload did not land — queued, will retry');
  case PictoSessionEndOutcome.nothingRecording:
    debugPrint('nothing was recording; a session had already ended');
  case PictoSessionEndOutcome.recordingPipelineAbsent:
    debugPrint('no recording pipeline — expected under test');
}

Reporting a bare false as "upload failed, queued for retry" is the mistake this exists to prevent: for nothingRecording both halves of that sentence are untrue. If you only need the yes/no, endSessionAndUpload is unchanged and still means what it always did.

Picto.setTag / Picto.recordEvent#

See Tags and Custom Events.

Picto.navigatorObserver#

A NavigatorObserver that records every route change as a screen. Takes no arguments and is the only screen-tracking wiring there is:

MaterialApp(navigatorObservers: [Picto.navigatorObserver], home: const HomePage());

Returns a fresh instance per read — Navigator asserts an observer isn't attached to two navigators at once — and the deduplication state is shared across them, so reading it more than once cannot double-record. setup cannot install this for you. See Screen Tracking.

Picto.retryPendingUploads()#

Retries every recording left over from a previous run that never made it past its own retries (offline, backend down, etc). Takes no arguments — each queued recording is stored alongside the metadata needed to send it, so nothing has to be supplied or remembered by your app.

You do not have to call this. The SDK drains the queue on its own: once during Picto.setup, and again whenever the app is foregrounded, throttled to at most one drain every 5 minutes. Earlier versions of this page told you to call it from main(); that line is now redundant and can be deleted. See Uploading a Session for the whole lifecycle this sits inside.

It stays public for the case the automatic triggers don't cover — draining at a moment you know is better than either of them:

// e.g. your own connectivity monitor has just reported the device is back online
unawaited(Picto.retryPendingUploads());

Never runs concurrently with itself. A call made while a drain is already in flight — yours, or one of the SDK's own — joins that drain instead of starting a second pass over the same queue, so a recording is never sent twice.

Never throws on an individual file: a queue that can't fully drain today is retried on the next launch.

Picto.isRecordingBindingInstalled#

Whether the recording binding is actually installed — i.e. whether startRecording() would capture anything. Always false in a widget test; see Testing.

RecordingUploader#

The lower-level transport Picto wraps — reach for this directly only if you need more control than the facade gives you (e.g. a custom retry policy).

Constructor parameters:

ParameterRequiredMeaning
apiKeyYesThe client app's API key — from the dashboard.
endpoint No A base URL to upload to, overriding the compiled-in one for this instance only. /db is appended internally, don't include it yourself. Exists for tests and tooling that hold a handle on a specific server; Picto.setup never sets it.
client No Inject your own http.Client (tests do this to mock network calls). Defaults to a real one.
pendingUploadsDir No Where a failed-after-all-retries upload gets queued for a later retry. Defaults to picto_pending_uploads/ under the system temp directory.
retryDelays No Backoff delays between retry attempts. Defaults to [1s, 4s, 16s].
sendTimeout No How long a single attempt waits for a response before treating it as a transient failure. Defaults to 120 seconds — multi-minute sessions base64 into multi-MB request bodies, and 30 was routinely too short.

apiKey is sent in the upload body and independently verified against the real client app record on the server — a spoofed or unrecognized value is rejected outright. The constructor takes no organization ID, client app ID, or account ID: all of that is fully determined by apiKey alone (one client app per key, one organization/owner per client app), so the server resolves and records it for you — nothing to look up in the dashboard, nothing to wire up here.

RecordingUploader.upload#

ParameterRequiredMeaning
path Yes The flushed recording file to upload — from PictoService.endSession/flush.
platformYese.g. defaultTargetPlatform.name.
appVersionYesYour app's version string.
formatVersion Yes Supplied for you by Picto.endSessionAndUpload — never hand-pick this.
durationMs Yes The recording's length: the shorter of CanvasRecorder.elapsedMs (the session's wall clock) and CanvasRecorder.contentEndMs (where the last captured event sits) at flush time. A trailing stretch in which nothing at all was captured — no frames, no input — is not part of the recording, so it is not declared and not billed; a screen that goes static while input keeps arriving is still content. Picto.endSessionAndUpload computes this for you.
sessionId Yes From the RecordingSegment returned by PictoService.endSession.
metadata No Free-form tags — see Tags.

| sequence | No | Which segment of sessionId this upload is, counting from 0 — the ordering key for a session that produced more than one. Defaults to 0, which is what a session with a single upload sends. Picto fills this in for you when it continues a session across a backgrounding; a host driving RecordingUploader itself supplies it or takes the default. Negative values are rejected by the server. See Sessions & Segments. |

CanvasRecorder#

Constructor parameterDefaultMeaning
startActive true Pass false to defer recording until you call startRecording() explicitly — Picto.setup always does this internally, so recording never auto-starts through the facade either way.
maxDuration package default This recording's own duration cap. Omit it to take the 10-minute package default; Picto.setup 's maxRecordingDuration is what sets it for a session the facade starts. See the duration cap .
errorWindowBefore package default This recording's own error-window lookback, over bytes this recorder already captured — it cannot reach back past startRecording . Omit it to take the 30-second package default; Picto.setup 's errorWindowBefore is what sets it for a session the facade starts. There is no errorWindowAfter here — the "after" half is a timer Picto owns, not state on a recorder, so a host driving PictoService itself decides when to end the session. See the error capture window .

Reference app --dart-define values#

This repo's own reference app (apps/mobile/lib/main.dart) reads its API key from a --dart-define at build time rather than hardcoding it — a reasonable pattern to copy, so a credential never ends up committed to source:

DefineMaps to
RECORDING_API_KEY Picto.setup's apiKey. Empty skips Picto entirely (see below).
flutter build apk --dart-define=RECORDING_API_KEY=oc_...

If RECORDING_API_KEY is empty, the reference app skips Picto entirely and falls back to the lower-level primitives it wraps — recording still works, it's just written locally with no credentials to upload under.

SDK --dart-define values#

Unlike the value above, this one is read by the picto SDK itself rather than by the reference app, so it applies to any host app that embeds picto:

DefineDefaultMaps to
PICTO_SAMPLING_PERCENTAGE 100 The share of app runs this build records — see Sampling . Clamped to 0..100 .
flutter build apk --dart-define=PICTO_SAMPLING_PERCENTAGE=25

Picto.setup also accepts a samplingPercentage parameter that defaults to whatever this define compiled in, for a host that computes the number at runtime instead. A rate the app's owner has set server-side — from App settings → Recording in the dashboard — replaces either of them for the rest of the run, so this define is the fallback rather than the last word. See Sampling for when a change to it takes effect.