Feature SDKs (Free)
app_blackbox Free (real recording: Enterprise)
- Tier
- Freeversion 2.10.4
- Command
archipelago generate app_blackbox- Generates
features/app_blackbox/app_blackbox_apifeatures/app_blackbox/app_blackbox_noop
- Generates first
- shared_identifiers, test_prereqwhen needed and missing, so there is nothing to install first
- Used by
- app_blackbox_recorder
Installs the
SessionRecorderSDKcontract and an always-inert no-op stub. Upgrade to theapp_blackbox_recorderEnterprise brick to actually record device metrics, filesystem events, and screen captures into a.bbxbundle.

New to Blackbox? Start with the Blackbox guide, which walks through recording a session and opening it in the viewer.
What it is
app_blackbox gives every generated project a stable, type-safe SessionRecorderSDK interface without any recording overhead. On its own this brick does not record anything — it exists so app code can call getIt<SessionRecorderSDK>() unconditionally, on any tier, and get a safe no-op back. Real session recording (device metrics, filesystem snapshots, custom events, optional screen capture) ships in the separate, Enterprise-only app_blackbox_recorder brick, which is not covered in depth here.
Variables
This brick has no generation-time variables. Recording options such as enableScreenRecording and enableFilesystemSnapshots are fields of the runtime BlackboxConfig, passed to SessionRecorderSDK.start(config: ...). See Public API below.
Usage
Interactive
archipelago generate app_blackboxNon-interactive (CI)
archipelago generate app_blackbox --config my_config.jsonGenerated Structure
features/
└── app_blackbox/
├── app_blackbox_api/
│ ├── lib/src/
│ │ ├── bundle/
│ │ │ ├── bundle_archive.dart
│ │ │ ├── bundle_writer.dart
│ │ │ └── in_memory_bundle_writer.dart
│ │ ├── models/
│ │ │ ├── blackbox_config.dart
│ │ │ ├── custom_event.dart
│ │ │ ├── filesystem_event.dart
│ │ │ ├── filesystem_event_type.dart
│ │ │ ├── filesystem_item.dart
│ │ │ ├── filesystem_snapshot.dart
│ │ │ ├── metric_sample.dart
│ │ │ ├── metric_type.dart
│ │ │ └── session_metadata.dart
│ │ └── session_recorder_sdk.dart # SessionRecorderSDK contract
│ └── test/
└── app_blackbox_noop/
├── lib/src/di/injector.dart # AppBlackboxPackageModule (registers nothing)
└── lib/testing.dart # no-op AppBlackboxTestPrerequisite (mirrors app_blackbox)No app_blackbox (orchestrator), app_blackbox_filesystem, app_blackbox_metrics, or app_blackbox_recording package is generated by this brick — those four belong to app_blackbox_recorder (Enterprise), a different brick.
Public API
abstract interface class SessionRecorderSDK() implements FeatureSDK {
bool get isRecording; // true while recording or paused
ValueListenable<RecordingState> get state; // idle, starting, recording, paused, stopping
ValueListenable<bool> get isScreenRecording; // hide amounts while true
ValueListenable<ScreenCaptureStatus> get screenCaptureStatus;
SessionMetadata? get currentSession; // null when isRecording is false
Stream<MetricSample> get metricStream; // broadcast; emits only while recording
Future<String> start({BlackboxConfig config = const BlackboxConfig()});
void captureCustomEvent(String name, {Map<String, dynamic>? properties});
Future<void> pause(); // captures nothing until resume()
Future<void> resume();
Future<BundleArchive?> stop(); // null when isRecording is false
Future<void> cancel();
}
// DI-free entry point for feature code. Drops every call until a recorder
// attaches; app_blackbox_noop never does, so it is safe in production.
abstract final class BlackboxEvents() {
static bool get isAttached;
static void attach(SessionRecorderSDK recorder); // the recorder calls it
static void detach(SessionRecorderSDK recorder);
static void captureCustomEvent(String name, {Map<String, dynamic>? properties});
}BlackboxConfig (all fields have defaults, so const BlackboxConfig() is valid):
const BlackboxConfig({
this.maxDurationSeconds = 300, // auto-stop; 0 disables
this.metricSampleIntervalMs = 1000,
this.enableScreenRecording = false,
this.maxScreenRecordingSeconds = 120, // video cap; 0 = none
this.screenRecordingBitRate = 2000000,
this.enableFilesystemSnapshots = true,
this.filesystemSnapshotIntervalSeconds = 30,
this.storageSampleIntervalSeconds = 10, // MetricType.storage pace
this.maxMetricSamples = 50000, // ring buffers: oldest evicted,
this.maxEvents = 10000, // counted in session.dropped
this.maxFilesystemEvents = 10000,
this.maxFilesystemSnapshots = 20,
this.maxFrameTimings = 36000, // per-frame timings; 0 turns them off
this.captureLifecycleEvents = true, // auto app_lifecycle events
this.filesystemExcludePatterns = defaultFilesystemExcludePatterns, // RegExp sources
this.enabledMetrics, // null = every signal
});
static const lightweight = BlackboxConfig(/* 5 s, no fps/jank/snapshots */);Session metadata
SessionMetadata (written to session.msgpack) carries, besides the session id and times:
appVersion,buildNumberandpackageNameof the host app (2.7.0). The cloud library usesappVersionfor its version column and Compare versions.deviceInfo: manufacturer, brand, model, OS name and release, Android API level, build id, emulator flag, RAM, storage, CPU cores, ABIs, screen, locale and time zone (2.7.0). It never holds a device identifier or a user-set device name.refreshRate,frameBudgetUsandbuildMode(2.8.0). Jank is judged against the display's own frame budget: 11.1 ms on a 90 Hz screen, 8.3 ms at 120 Hz. Bundles without these fields use one 60 Hz frame (16.7 ms).amountsMasked(2.6.0):truewhen the session has a screen recording.
summary.json is version 2 since 2.8.0. It counts jank against the display budget (jankFrames) and against 60 Hz (jankFrames60).
Metric semantics
In bundle schema v2, MetricType.cpu and MetricType.memory measure the app process on both platforms:
cpuis the process CPU time over (the wall time × the core count), as a percent from 0 to 100.memoryis in bytes. Android reports PSS and iOS reportsphys_footprint.
In v1, Android reported values for the whole device. battery is a percent from 0 to 100. The full unit table and the platform gaps are in app_blackbox_api/BUNDLE_FORMAT.md (copied in the recorder page).
Bundle retention (opt-in)
stop() returns bytes and never writes to disk. If you keep bundles on the device, BundleStore lists them, rotates them and reports their disk use. Nothing runs until you call it:
final store = BundleStore(Directory('${docs.path}/blackbox'));
await store.save(archive, maxCount: 20, maxAge: const Duration(days: 7));
final bundles = await store.list(); // newest first: file, sizeBytes, modified
final used = await store.totalBytes();
await store.deleteOlderThan(const Duration(days: 3));
await store.deleteBeyondCount(5);Reading a saved bundle: readBundleInfos
readBundleInfos(paths) reads start time, stop time, recorded time (paused time left out), event count and whether there is a video, for each .bbx path, on a background isolate. It inflates session.msgpack only; the video is never read. Bundles from 2.5.0 carry eventCount there; for older ones events.msgpack is counted. An unreadable file gives null.
Labels: BlackboxLabels
The strings of the recorder's floating control and sessions list, English by default. app_blackbox_recorder sets BlackboxLabels.builder to its own English and Indonesian translations when the host has not set one. Set it from host code to use your own translations. See app_blackbox_recorder.
Host settings: BlackboxHost
Set from host code before initializeInjector(); nothing reads them in the production flavor (2.10.0):
resultPresenter— aBlackboxResultPresenterthat shows a stop result (BlackboxResult: localizedmessage,isError, optionalonView) in the app's own UI. Returnfalseto keep the recorder's card or snackbar.storageDirectory— the directory that holdsblackbox/(saved sessions, upload outbox, control settings).nullkeeps app-private storage.
Cloud sync: BundleUploader
app_blackbox_api also holds the BundleUploader contract. The recorder brick implements it in internal-flavor builds. It uploads each finished .bbx bundle to your own bucket. In the free tier and in production-flavor builds, isConfigured is false and nothing is uploaded.
The contract has these members: isConfigured, endpointHost, autoSync, wifiOnly, items (the outbox stream), enqueue, requestDrain, syncNow, clearFailed and purge. Errors never throw out of the uploader. They show in items. See the Blackbox cloud sync guide for setup.
In the free/pro tier, this is a safe no-op
AppBlackboxPackageModule (app_blackbox_noop) registers one inert SessionRecorderSDK in GetIt (since 2.6.0):
class AppBlackboxPackageModule extends MicroPackageModule {
@override
FutureOr<void> init(GetItHelper gh) {
gh.lazySingleton<SessionRecorderSDK>(SessionRecorderSDKImpl.new);
}
}Every method is a no-op, isRecording and isScreenRecording are always false, and it does not join FeatureSDKRegistry (no routes). It exists so always-on host code can read the screen-recording privacy signal, getIt<SessionRecorderSDK>().isScreenRecording, in production-flavor builds and in projects without the recorder. Before 2.6.0 it registered nothing, and getIt<SessionRecorderSDK>() threw there.
DI and wiring
post_gen.dartaddsfeatures/app_blackbox/app_blackbox_apiandapp_blackbox_noopto the Dart workspace, adds both as path dependencies to the host app'spubspec.yaml, and registersAppBlackboxPackageModule(frompackage:app_blackbox_noop/app_blackbox_noop.dart) intolib/di/injector.dartviaServiceLocatorUpdater.- The noop's DI module class is deliberately named
AppBlackboxPackageModule— the same name the paidapp_blackbox_recorderimpl uses for its own module. The two packages are never imported together (the enterprise brick'sbuild_prepare.yamlswap changes which one the app depends on), so upgrading from free to Enterprise is a plain import-path rewrite, not a class-reference rewrite. - The brick records itself as installed in
archipelago.yamlviaArchipelagoConfigHelper.appendBrickInstalled('app_blackbox').
Dependencies
- None outside the base monorepo. Before 1.3.0,
app_blackbox_apiandapp_blackbox_noophad an unused path dependency onapp_debugger_api. Neither package imported it, and generation broke in projects withoutapp_debugger. 1.3.0 removed it. feature_sdk— theFeatureSDKmarker interfaceSessionRecorderSDKimplements.shared/identifiers—app_blackbox_noopdepends on it.pre_gengenerates the freeshared_identifiersbrick whenshared/identifiers/pubspec.yamlis missing (2.8.2 and later).utilities/test_prereq—app_blackbox_noop/testing.dartimplements itsTestPrerequisite, sobuild-prepare --flavor productioncan rewrite the app'spackage:app_blackbox/testing.dartimport to the noop.pre_gengenerates thetest_prereqbrick when it is missing (2.10.0 and later).
Upgrading to real recording
archipelago generate app_blackbox_recorderThis Enterprise add-on generates the real orchestrator plus native capability packages (app_blackbox_filesystem, app_blackbox_metrics, app_blackbox_recording) and wires the build_prepare.yaml swap so the real implementation is active in internal flavors (development, staging, alpha, beta) and removed from the production flavor. It is a separate brick and out of scope for this page.
Upgrading to 2.0.0
2.0.0 adds BundleWriter.setFrameTimings, so a class that implements BundleWriter itself must add that method. InMemoryBundleWriter, the only writer that Archipelago ships, already has it. app_blackbox_recorder 1.5.0 and later needs app_blackbox 2.0.0. Upgrade app_blackbox first, because the recorder's pre_gen stops when it finds an older one.
Upgrading to 2.9.0
2.9.0 adds BundleWriter.setLaunchTrace(Map), which writes the optional launch_trace.json entry (LaunchTrace v1). A class that implements BundleWriter itself must add that method. app_blackbox_recorder 2.0.0 and later needs app_blackbox 2.9.0.
Upgrading to 2.10.0
2.10.0 adds LaunchTraceSource, the port the recorder reads the launch trace through. Blackbox owns it, so no Blackbox package depends on the launch tracker. The host app registers an implementation in GetIt; when none is registered, bundles have no launch_trace.json. Nothing to change in your code. app_blackbox_recorder 2.0.0 and later needs app_blackbox 2.10.0.
Upgrading over changed packages
From 2.10.0, post_gen records the files it generated in .archipelago/generated/app_blackbox.json, and pre_gen stops with Nothing was changed when app_blackbox_api or app_blackbox_noop changed since then (changed, added or deleted files). A project generated before 2.10.0 has no record and stops once. Merge the new version in, or set ARCHIPELAGO_FORCE=1 to overwrite. See Upgrading generated packages.
Limitations
- No recording capability at all in this brick. Every
SessionRecorderSDKcall is a no-op untilapp_blackbox_recorderis installed, and stays a no-op in production-flavor builds by design.
Changelog
Run archipelago brick info app_blackbox to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.