Skip to content

Persistence

PoseFlow Studio doesn’t know about Firebase, SQLite, or any specific storage backend. It talks to a small set of port interfaces; you implement them against whatever your host already uses.

This page is the wiring contract.

The StudioPersistence bundle

class StudioPersistence {
final StudioCatalog catalog;
final StudioAudit audit;
final StudioPhaseRepository phases;
final StudioVersionRepository versions;
}

Four small interfaces. The bundle wraps them so you can pass a single value through StudioScope.

StudioCatalog

The main read/write port for movements.

abstract class StudioCatalog {
Future<List<MovementSummary>> listSummaries({String? domainFilter});
Future<Movement?> load(String id);
Future<void> save(
Movement movement, {
required String domain,
String status,
});
String export(Movement movement); // → JSON string
}

The Studio calls save() on every commit and load() when the editor opens with a movement id. listSummaries() powers the “Open” picker dialog.

StudioAudit

Append-only event log. Optional but recommended.

abstract class StudioAudit {
Future<void> log(StudioAuditEvent event);
}

The Studio logs save / publish / import / export events. Your host can surface them to admins for traceability or just discard them (no-op implementation).

StudioPhaseRepository

Some host backends keep phase definitions in a separate table / collection alongside the .pose file (typically so phase queries can hit secondary indexes directly without parsing the whole document). The repository lets the Studio replace-all the phases for a movement on every save.

abstract class StudioPhaseRepository {
Future<void> replaceAll(String movementId, List<PhaseDefinition> phases);
Future<List<PhaseDefinition>> list(String movementId);
Future<void> deleteForMovement(String movementId);
}

For hosts that don’t need this (the .pose file already carries phases), a no-op implementation is fine.

StudioVersionRepository

Snapshot history for rollback.

abstract class StudioVersionRepository {
Future<void> snapshot(String movementId, Movement movement);
Future<List<MovementSnapshot>> list(String movementId);
}

The Studio snapshots every save. Your host can expose “previous versions” in its UI or just discard (no-op).

InMemoryStudioPersistence

For tests, demos, and standalone Studio installations without a backend, ship in-package:

import 'package:pose_flow_studio/pose_flow_studio.dart';
final persistence = InMemoryStudioPersistence().bundle();

Stores everything in a Map. Survives the app session but not restarts.

StudioScope

Exposes the bundle to the widget tree via InheritedWidget:

import 'package:pose_flow_studio/pose_flow_studio.dart';
StudioScope(
persistence: persistence,
assetBasePath: 'pose_flow/', // optional, for web pose worker assets
metadataPanelBuilder: (context, movementId) => YourMetadataPanel(),
child: const PoseFlowStudioScreen(),
)

Hooks

  • persistence: required. The bundle.
  • assetBasePath: optional. Where the web pose pipeline finds its WASM worker + weights. See web integration.
  • metadataPanelBuilder: optional. The Studio renders your panel inside the Phases column’s Metadata slot (host-specific fields like movement name, body parts, image URLs). When null, the slot is hidden.

Wiring a cloud backend

A common shape: Firestore + Cloud Storage. Each adapter implements one of the Studio ports:

class FirestoreStudioCatalog implements StudioCatalog {
// Writes the Movement to
// `movements/{id}/movement.pose` in Cloud Storage; stamps
// `pose_file_url` + `studio_authored: true` on the Firestore
// doc.
}
class FirestoreStudioPhaseRepository implements StudioPhaseRepository {
// Mirrors phases to a `phase_definitions` array on the
// movement doc (for query indexing).
}
class FirestoreStudioVersionRepository implements StudioVersionRepository {
// Snapshots into `movements/{id}/studio_versions/{timestamp}`.
}
class FirestoreStudioAudit implements StudioAudit {
// Writes to a `studio_audit_events` collection.
}

A single factory wires them up:

StudioPersistence buildStudioPersistence() {
return StudioPersistence(
catalog: FirestoreStudioCatalog(),
audit: FirestoreStudioAudit(),
phases: FirestoreStudioPhaseRepository(),
versions: FirestoreStudioVersionRepository(),
);
}

The router then wraps the Studio route:

StudioScope(
persistence: buildStudioPersistence(),
metadataPanelBuilder: (ctx, id) => YourMetadataPanel(id: id),
child: const PoseFlowStudioScreen(),
)

The adapter layer is the only cloud-aware code; the Studio package itself stays storage-agnostic.

Wiring a Drift / SQLite backend

For local-only authoring (no cloud), use Drift or any other Flutter SQL package, same shape, adapters implementing the Studio ports against the local DB. Useful for offline-friendly trainer tools or single-author desktop workflows.

Storage-agnostic guarantee

Search packages/pose_flow_studio/lib/ for firebase, firestore, cloud_firestore, drift, sqflite, zero hits. The package has zero awareness of any specific storage. This is enforced by code review + the audit; future adapters slot in purely additively.