`StudioScope`
StudioScope is the InheritedWidget PoseFlow Studio screens use to
reach their backing store. Hosts wrap their Studio routes in a
StudioScope at the router level, screens read their persistence
via StudioScope.of(context) rather than threading it through
every constructor.
Lives in package:pose_flow_studio, you only need this when
building a Studio host, not when consuming PoseFlow on the
runtime side. See Building a Studio host
for the full integration pattern.
Shape
class StudioScope extends InheritedWidget { const StudioScope({ super.key, required this.persistence, this.assetBasePath = 'pose_flow/', this.metadataPanelBuilder, required super.child, });
final StudioPersistence persistence; final String assetBasePath; final Widget Function(BuildContext context, String exerciseId)? metadataPanelBuilder;}Fields
| Field | Meaning |
|---|---|
persistence | The StudioPersistence bundle, phase repository, version repository, catalog, audit log. Hosts implement these against their preferred backend (Firestore, Drift, in-memory, etc.). See Studio persistence. |
assetBasePath | Web-only, directory under web/ containing the pose engine worker + WASM + weights. Defaults to pose_flow/. Override if your host serves the assets from a different path. |
metadataPanelBuilder | Optional host hook for a metadata-editor side panel. When supplied, PoseFlowStudioScreen renders the returned widget as a floating panel on the right side so the trainer can edit movement-tracking and CMS-side metadata (body part, muscle groups, hero video URL, etc.) without leaving the Studio. The CMS wires this to its ExerciseMetadataPanel widget; the standalone showcase leaves it null and the panel is suppressed. |
Usage
Wrap your Studio routes
import 'package:pose_flow_studio/pose_flow_studio.dart';
GoRoute( path: '/studio/:id', builder: (context, state) => StudioScope( persistence: myStudioPersistence, assetBasePath: 'blazeflow/', metadataPanelBuilder: (ctx, id) => ExerciseMetadataPanel(id: id), child: PoseFlowStudioScreen( movementId: state.pathParameters['id'], ), ),);Read from a Studio screen
final persistence = StudioScope.of(context);final movements = await persistence.catalog.list();There’s also StudioScope.maybeOf(context), returns null instead
of asserting when no scope is in the tree. Useful for screens that
support both router-wired (production) and direct constructor-
injected (test) wiring.
Read the asset base path
final base = StudioScope.assetBasePathOf(context);// 'pose_flow/' for showcase, 'blazeflow/' for CMSCameraTrackingView and the web the pose engine worker loader call this
to resolve their asset URLs. Falls back to the package default when
no scope is in context.
The StudioPersistence bundle
StudioPersistence groups the four backing repositories the Studio
needs:
| Field | Purpose |
|---|---|
phases | Read / write the canonical .pose document per movement. |
versions | History of phase-document versions for undo + rollback. |
catalog | The list view: movement summaries (id, name, last modified). |
audit | Append-only log of authoring actions for the CMS audit panel. |
Hosts pick implementations per backend. The CMS wires Firestore- backed implementations; the standalone showcase wires Drift (SQLite) for offline-friendly authoring; tests wire in-memory stubs. See Studio persistence for the backend contract.
Why InheritedWidget?
The Studio screens form a tall widget tree (router → scaffold →
editor → panel → field), and threading the persistence bundle
through every constructor is friction without value.
InheritedWidget is Flutter’s standard way to expose ambient
context, StudioScope.of(context) is constant-time and re-builds
only the widgets that explicitly subscribe.
The CMS and standalone showcase both wire via this scope; tests typically pass persistence directly via the screen’s optional constructor parameter to avoid the InheritedWidget wrapper.
Read next
- Studio persistence, the backend contract.
- Building a Studio host, full host integration walk-through.
- Studio overview, what the Studio is and what it ships.