`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
| Field | Meaning |
|---|---|
message | The cue text authored on the rule’s FormFeedback.text (e.g. “Go deeper”). Ready to render directly. |
severity | FormRuleSeverity, error (red), warning (amber), info (neutral). Use it to colour the toast / prioritise the cue stack. |
trackingPoint | The id of the TrackingPoint the rule references, useful when you want to highlight the relevant joint in your overlay. |
currentValue | The scalar value the rule evaluated against at the moment of firing. Useful for debug HUDs and trainer-facing diagnostics. |
timestamp | Wall-clock fire time. Use this for de-duping cues across very-fast streams. |
Severity
FormRuleSeverity is a three-step enum:
| Value | Convention |
|---|---|
error | Form deviation that could cause injury or invalidates the rep. Red. Trainers expect users to fix immediately. |
warning | Suboptimal form. Amber. The rep still counts but quality drops. |
info | Cue 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:
| Trigger | Fires when |
|---|---|
FeedbackTrigger.onViolation (default) | The rule’s FormCondition is violated. |
FeedbackTrigger.onCompliance | The rule’s FormCondition is satisfied, useful for “Nice depth” cues that celebrate good form. |
FeedbackTrigger.always | The rule’s phase is active. Pass/fail irrelevant. Useful for general cues like “Brace your core”. |
Patterns
Render a toast deck
StreamSubscription<FormFeedbackEvent>? _sub;
@overridevoid 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; }});Read next
FormRule, what the rule shape looks like when authored.- Form analysis, the stabilisation window + dispatch logic.
TrackingResult,feedbackcarries the same messages in a deduplicated list for per-frame rendering.