Activity uploads to Intervals.icu (and potentially other platforms) showed incorrect workout durations - typically 2 minutes shorter than the actual workout time displayed in the app for a 30-minute workout.
The workout system maintained two independent time tracking mechanisms:
elapsedSeconds+_previouslyElapsedTime: Tracked wall-clock time usingDateTime.now(), including system overhead_workoutProgressTime: Incremented by exactly 0.1s per timer tick (the "intended" workout time)- Track points: Used
DateTime.now()timestamps
This created a discrepancy:
- App UI showed
elapsedSeconds(wall-clock time with overhead) - Exported FIT files calculated duration from track point timestamps (also wall-clock time)
- Timer overhead accumulated over time, causing the ~2 minute drift
Refactored to use _workoutProgressTime as the single source of truth:
- ✅ Converted
elapsedSecondsfrom variable to computed getter:int get elapsedSeconds => _workoutProgressTime.round() - ✅ Removed
_previouslyElapsedTimevariable (no longer needed) - ✅ Changed
_lastTrackPointTimefromDateTime?todouble(workout progress seconds) - ✅ Updated track point timestamp calculation:
_workoutStartTime + Duration(milliseconds: workoutProgressTime * 1000) - ✅ Simplified pause/resume logic (no more wall-clock segment tracking)
- ✅ Removed overhead-prone
DateTime.now()calls during workout execution
- ✅ Removed
elapsedSecondsparameter from state persistence - ✅ Removed
_elapsedSecondsKeyconstant - ✅ Cleaned up state save/load logic
- ✅ Workout duration consistency across app display and file uploads
- ✅ Eliminated ~2 minute time drift for 30-minute workouts
- ✅ Track point timestamps now accurately reflect workout progress
- ✅ FIT file durations match displayed elapsed time
- ✅ Pause/resume functionality works correctly
- ✅ Background workout continuation works
- ✅ Workout state persistence works
- ✅ Navigation away from workout screen and back works
- ✅ All existing features maintain compatibility
- Net -26 lines of code (simplified by removing duplicate system)
- 0 new warnings from
flutter analyze - All tests pass (existing + new comprehensive tests)
- Better maintainability (single source of truth is easier to debug)
- test/erg_workout_test.dart: Validates GPX/FIT file generation (passing)
- test/timing_consistency_test.dart: Validates time consistency (new, passing)
- Elapsed seconds equals workout progress seconds
- Track point timestamps based on workout progress
- Time tracking consistent after pause/resume
-
Complete 30-minute workout without pausing
- Verify app shows 30:00 elapsed time
- Upload to Intervals.icu
- Verify uploaded workout shows 30:00 duration ✓
-
Complete 30-minute workout with 2 pause cycles
- Verify app shows 30:00 elapsed time
- Upload to Intervals.icu
- Verify uploaded workout shows 30:00 duration ✓
-
Navigate away and back during workout
- Start workout, wait 10 minutes
- Navigate to another screen
- Navigate back to workout screen
- Verify timer continues correctly
- Complete workout and upload
- Verify uploaded duration matches ✓
See TIMING_FIX_EXPLANATION.md for detailed technical documentation including:
- Architecture comparison (before/after)
- Code flow diagrams
- Migration notes
- Testing guidelines
- No user action required - change is transparent to end users
- No data migration needed - existing workout states load correctly
- Backward compatible - old saved states are gracefully handled
Fixes issue reported in #85 by @Lunchtime0614