Logopicto

Quick Start

Wire Picto into your app's main(), start a recording, and let it upload itself.

This walks through the minimum needed to record a session and get it uploaded, using Picto, the SDK's public embedding facade. It mirrors exactly what this repo's own reference app does in apps/mobile/lib/main.dart — if anything here is unclear, that file is the complete, working version to read alongside it.

1. Initialize Picto#

One call, before runApp():

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

void main() {
  Picto.setup(
    apiKey: 'oc_...',   // from your client app in the dashboard
    appVersion: '1.0.0',
  );

  runApp(const MyApp());
}

That's the entire backend configuration — no server URL, organization ID, client app ID, or account ID anywhere. Uploads go to picto's hosted backend (https://api.picto.dev), which the SDK already knows.

This is the one call main() genuinely has to make, and it replaces WidgetsFlutterBinding.ensureInitialized() if you were calling it yourself — call it before runApp(). Nothing else about your app changes: no required UI, no behavior change, unless you opt into what's below.

Order matters, and it is the single most common way picto ends up recording nothing. Picto.setup() has to be your app's WidgetsBinding, so it must come before any other binding initialization — including one inside a plugin you call first. A debug build now tells you outright when it didn't.

Don't hardcode apiKey in source — the reference app reads it via --dart-define at build time instead (RECORDING_API_KEY), which keeps it out of version control. See Configuration Reference.

2. Start recording — explicitly, on your own terms#

Picto.setup does not start a recording. Nothing does — with one exception, covered below — until you call:

Picto.startRecording();

This is deliberate: apart from the error case below, Picto never records or uploads a session unless your own code decides to. Call it wherever makes sense for your app — unconditionally at launch, behind a consent screen, from a debug menu, after a user opts in to "help us improve the app," whatever fits. A no-op if already recording.

The exception is an uncaught error. Picto.setup installs error hooks as part of that one call, and if an error reaches them while nothing is recording, picto starts a session for it — so a crash that happens before you ever call startRecording() is still captured rather than silently dropped. Your own error handling is unaffected. See Errors That Start a Recording.

3. Uploading happens automatically from here#

Once a recording is active, you don't need to do anything else: if the app is backgrounded or terminated, Picto ends the recording and uploads it on its own. Failures aren't lost — a failed upload is queued locally, and the SDK drains that queue itself at the next launch and whenever the app is foregrounded.

That one startRecording() keeps capturing for the rest of the process, too. Foregrounding the app after a backgrounding starts recording again by itself, continuing the same session as its next segment, so you do not have to call anything on every app switch — see Sessions & Segments.

Uploading a Session is the whole of it in one page: the three things that trigger an upload, what one upload actually contains, what is never uploaded at all, and what happens to a recording that cannot be sent.

4. (Optional) end and upload manually#

If you want an explicit trigger instead of waiting for the app to background — e.g. a "Send feedback" button, or ending the session on sign-out — call:

final ok = await Picto.endSessionAndUpload();

ok is true once the upload has actually succeeded (false means it's queued for retry, not lost). Pass metadata for any extra free-form tags you want stored alongside the recording — see Tags.

Watching the result#

Once the upload succeeds, sign in to the dashboard, select your organization and client app, and click Watch replay next to the recording — see Watching a Replay.

Nothing is being recorded#

If your replays are empty, or endSessionAndUpload() keeps returning false, the cause is almost always that picto is not the live WidgetsBinding. (First rule out the ordinary case: false also means "there was no session left to end", which is not a failure — Picto.endSessionAndUploadOutcome() tells the two apart.) Picto has to be the binding to see draw calls, so when something else owns it the SDK is completely inert — a session starts, runs, ends, and is discarded rather than uploaded.

A debug build tells you this directly. Just after the first frame, picto checks whether the recording binding is actually live and throws a FlutterError if it is not:

Picto is not recording: the recording binding is not the live WidgetsBinding.

There are two causes, and the error names both:

  • Something initialized a binding before Picto.setup() — your own WidgetsFlutterBinding.ensureInitialized(), or one inside a plugin you call first. Move Picto.setup() above every other binding initialization in main().
  • Something replaced the binding afterwards — flutter_driver's enableFlutterDriverExtension() and integration_test each construct a binding of their own. Recording genuinely cannot work alongside them; exercise it by running your app for real. See Testing.

The check runs after the first frame rather than inside Picto.setup() precisely so that it catches both cases with one test. It throws from a post-frame callback so the framework reports it through FlutterError.onError — you get a loud, attributed error and an app that still runs, instead of a main() that dies before anything is drawn.

This error is debug-only and is compiled out of profile and release builds entirely. Those builds print the same warning to the console instead. It also never fires in a widget test — standing down under flutter_test is correct behaviour, not a misconfiguration.

You can check the same fact yourself at any point:

if (!Picto.isRecordingBindingInstalled) {
  // picto is inert — nothing this run will be captured or uploaded
}