Skip to content

`FormFeedbackEvent`

A FormFeedbackEvent is what comes out of MovementTracker.onFeedback when a FormRule fires. Subscribers render it into toast deck cues, voice-over prompts, or haptic patterns.

tracker.onFeedback.listen((event) {
showToast(
text: event.message,
severity: event.severity,
);
});

Shape

class FormFeedbackEvent {
final String message; // cue text to render
final FormRuleSeverity severity; // error / warning / info
final String trackingPoint; // id of the rule's tracking point
final double currentValue; // the value that triggered
final DateTime timestamp;
}

Fields

FieldMeaning
messageThe cue text authored on the rule’s FormFeedback.text (e.g. “Go deeper”). Ready to render directly.
severityFormRuleSeverity, error (red), warning (amber), info (neutral). Use it to colour the toast / prioritise the cue stack.
trackingPointThe id of the TrackingPoint the rule references, useful when you want to highlight the relevant joint in your overlay.
currentValueThe scalar value the rule evaluated against at the moment of firing. Useful for debug HUDs and trainer-facing diagnostics.
timestampWall-clock fire time. Use this for de-duping cues across very-fast streams.

Severity

FormRuleSeverity is a three-step enum:

ValueConvention
errorForm deviation that could cause injury or invalidates the rep. Red. Trainers expect users to fix immediately.
warningSuboptimal form. Amber. The rep still counts but quality drops.
infoCue or encouragement (e.g. “Nice depth”). Neutral. Doesn’t penalise the form score.

MovementTracker orders concurrent feedback by severity when populating TrackingResult.feedback (the deduplicated list for display), so the highest-severity cue wins the spot when several rules fire on the same frame.

When does it fire?

Once per rule activation. The form service stabilises feedback with a ~0.4 s “must be true for 12 consecutive frames” window and enforces a 2-second minimum interval between cue changes, see Form analysis. You won’t see the same cue flap on and off; it locks in for at least 2 seconds once stable.

The triggering condition depends on the rule’s FormFeedback.trigger:

TriggerFires when
FeedbackTrigger.onViolation (default)The rule’s FormCondition is violated.
FeedbackTrigger.onComplianceThe rule’s FormCondition is satisfied, useful for “Nice depth” cues that celebrate good form.
FeedbackTrigger.alwaysThe rule’s phase is active. Pass/fail irrelevant. Useful for general cues like “Brace your core”.

Patterns

Render a toast deck

StreamSubscription<FormFeedbackEvent>? _sub;
@override
void initState() {
super.initState();
_sub = widget.tracker.onFeedback.listen((event) {
setState(() {
_activeCues.add(event);
// Auto-evict after 3 seconds.
Timer(const Duration(seconds: 3), () {
setState(() => _activeCues.remove(event));
});
});
});
}

Vibrate on error-severity cues

tracker.onFeedback.listen((event) {
if (event.severity == FormRuleSeverity.error) {
HapticFeedback.heavyImpact();
}
});

Voice-over the most recent cue

String? _lastSpoken;
tracker.onFeedback.listen((event) {
if (event.message != _lastSpoken) {
flutterTts.speak(event.message);
_lastSpoken = event.message;
}
});
  • FormRule, what the rule shape looks like when authored.
  • Form analysis, the stabilisation window + dispatch logic.
  • TrackingResult , feedback carries the same messages in a deduplicated list for per-frame rendering.