Skip to content

Quick start, track a squat in 30 lines

Drop a tracked movement view into any Flutter screen and start counting reps against a bundled .pose file.

1. Get a .pose file

For this quick start, drop any authored .pose file into your app’s assets/ directory, or author one with PoseFlow Studio. A bundled squat fixture is the easiest way to verify the integration end-to-end before authoring your own.

A .pose file is a single JSON document with formatVersion containing every phase, tracking point, and form rule the runtime needs. See the .pose file spec for the exact shape.

2. Mount TrackedMovementView

TrackedMovementView is the single drop-in widget that does the full loop, camera, pose detection, rep counting, form analysis, skeleton overlay. You hand it a Movement (the in-memory model parsed from your .pose file) and subscribe to its callbacks.

import 'dart:convert';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pose_flow/pose_flow.dart';
class SquatPage extends StatefulWidget {
const SquatPage({super.key, required this.cameras});
final List<CameraDescription> cameras;
@override
State<SquatPage> createState() => _SquatPageState();
}
class _SquatPageState extends State<SquatPage> {
Movement? _exercise;
int _reps = 0;
double _formScore = 100;
@override
void initState() {
super.initState();
_loadExercise();
}
Future<void> _loadExercise() async {
final raw = await rootBundle.loadString('assets/squat.pose');
setState(() {
_exercise = Movement.fromJson(
jsonDecode(raw) as Map<String, dynamic>,
);
});
}
@override
Widget build(BuildContext context) {
final movement = _exercise;
if (movement == null) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: Stack(
children: [
TrackedMovementView(
cameras: widget.cameras,
movement: movement,
onRepCompleted: (event) {
setState(() => _reps = event.repNumber);
},
onTrackingResult: (result) {
setState(() => _formScore = result.formScore);
},
),
Positioned(
top: 60,
left: 24,
child: Text(
'Reps: $_reps Form: ${_formScore.toStringAsFixed(0)}',
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}

That’s the full integration. Point the camera at yourself, do a squat, the counter ticks.

What just happened

  1. Movement.fromJson parsed the .pose file into an in-memory movement (phases, tracking points, form rules, rep-detection strategy).
  2. TrackedMovementView created a MovementTracker internally, loaded the movement via the canonical LoadMovementArgs.from(...).applyTo(tracker) builder, and wired the pose-detection pipeline.
  3. Every frame: the camera emits a Pose → tracker processes it → the phase machine advances → the rep counter ticks when a phase sequence completes → onRepCompleted fires.
  4. The same per-frame result carries the live form score, position label, phase id, and a pose field with the raw landmarks if you need them.

Next steps