Skip to content
Back to archipelago.banuacoder.com

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_api
  • features/app_blackbox/app_blackbox_noop
Generates first
shared_identifiers, test_prereqwhen needed and missing, so there is nothing to install first

Installs the SessionRecorderSDK contract and an always-inert no-op stub. Upgrade to the app_blackbox_recorder Enterprise brick to actually record device metrics, filesystem events, and screen captures into a .bbx bundle.

The in-app debugger as a full-screen tool list, and the Blackbox recorder tool recording a session

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 ​

bash
archipelago generate app_blackbox

Non-interactive (CI) ​

bash
archipelago generate app_blackbox --config my_config.json

Generated Structure ​

text
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 ​

dart
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):

dart
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, buildNumber and packageName of the host app (2.7.0). The cloud library uses appVersion for 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, frameBudgetUs and buildMode (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): true when 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:

  • cpu is the process CPU time over (the wall time × the core count), as a percent from 0 to 100.
  • memory is in bytes. Android reports PSS and iOS reports phys_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:

dart
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 — a BlackboxResultPresenter that shows a stop result (BlackboxResult: localized message, isError, optional onView) in the app's own UI. Return false to keep the recorder's card or snackbar.
  • storageDirectory — the directory that holds blackbox/ (saved sessions, upload outbox, control settings). null keeps app-private storage.

See app_blackbox_recorder.

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):

dart
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.dart adds features/app_blackbox/app_blackbox_api and app_blackbox_noop to the Dart workspace, adds both as path dependencies to the host app's pubspec.yaml, and registers AppBlackboxPackageModule (from package:app_blackbox_noop/app_blackbox_noop.dart) into lib/di/injector.dart via ServiceLocatorUpdater.
  • The noop's DI module class is deliberately named AppBlackboxPackageModule — the same name the paid app_blackbox_recorder impl uses for its own module. The two packages are never imported together (the enterprise brick's build_prepare.yaml swap 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.yaml via ArchipelagoConfigHelper.appendBrickInstalled('app_blackbox').

Dependencies ​

  • None outside the base monorepo. Before 1.3.0, app_blackbox_api and app_blackbox_noop had an unused path dependency on app_debugger_api. Neither package imported it, and generation broke in projects without app_debugger. 1.3.0 removed it.
  • feature_sdk — the FeatureSDK marker interface SessionRecorderSDK implements.
  • shared/identifiers — app_blackbox_noop depends on it. pre_gen generates the free shared_identifiers brick when shared/identifiers/pubspec.yaml is missing (2.8.2 and later).
  • utilities/test_prereq — app_blackbox_noop/testing.dart implements its TestPrerequisite, so build-prepare --flavor production can rewrite the app's package:app_blackbox/testing.dart import to the noop. pre_gen generates the test_prereq brick when it is missing (2.10.0 and later).

Upgrading to real recording ​

bash
archipelago generate app_blackbox_recorder

This 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 SessionRecorderSDK call is a no-op until app_blackbox_recorder is 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.