Workout Tracking Implementation in Mobile Application
Workout tracking — managed session with multiple parallel data streams: GPS, accelerometer/gyroscope, heart rate (from wearable), barometer (altitude). Must record all synchronously, display realtime and save without loss on app crash or battery drain.
Workout Session Architecture
Central element — WorkoutSession (or your name), state machine with states:
Idle → Preparing → Active → Paused → Active → Finishing → Saved
Transitions triggered by user (Start/Pause/Finish buttons) and system (GPS loss, low battery). All state stored in WorkoutRepository, persisted via Room (Android) or CoreData/SQLite (iOS) after each update — recover on crash.
@Entity
data class WorkoutPoint(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val sessionId: String,
val timestamp: Long,
val latitude: Double?,
val longitude: Double?,
val altitude: Double?,
val heartRate: Int?,
val speed: Double?,
val distance: Double
)
Every 5 seconds insert new record in DB. On finish — aggregate all into WorkoutSummary. Don't delete intermediate points — needed for track rendering.
GPS Tracking and Accuracy
GPS Noise Filtering
Raw GPS data contains ±5–15 m noise. Visually on route looks like "zigzags" instead straight segment. Filter via Kalman filter — most adequate method for GPS.
For mobile development no need implement Kalman from scratch. On Android — FusedLocationProviderClient already applies internal filtering. On iOS — CLLocationManager with kCLLocationAccuracyBestForNavigation uses sensor fusion. Additionally: discard points with horizontalAccuracy > 20 m.
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last,
location.horizontalAccuracy <= 20,
location.horizontalAccuracy >= 0 else { return }
if let previous = lastLocation {
let segment = location.distance(from: previous)
totalDistance += segment
}
lastLocation = location
trackPoints.append(location)
}
Distance and Pace Calculation
Distance — sum of distances between sequential GPS points (CLLocation.distance(from:) on iOS, Location.distanceTo() on Android). Pace (min/km) = 1000 / speed (m/s) / 60. Speed from CLLocation.speed / Location.speed — calculated via Doppler shift, more accurate than coordinate difference.
At speed < 0 (no reliable data) — use speed from coordinates.
Background Execution
iOS
Add to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
Request Always permission. Without this GPS stops ~15 seconds after app backgrounding. allowsBackgroundLocationUpdates = true on CLLocationManager — mandatory.
Android
Foreground Service with notification (otherwise Android kills process on memory shortage):
class WorkoutTrackingService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildTrackingNotification()
startForeground(NOTIFICATION_ID, notification)
startLocationUpdates()
return START_STICKY
}
}
START_STICKY — system restarts service if killed, with null intent. Handle null and restore state from Room.
Wearable Integration
Heart rate from Apple Watch — via HKWorkoutBuilder on iOS (Watch auto-adds samples). On Android — Wear OS via HealthServicesClient or Bluetooth GATT with Heart Rate profile (UUID 0x180D).
Bluetooth GATT for external sensors (Polar H10 chest strap, Wahoo TICKR):
val hrServiceUUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")
val hrCharacteristicUUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
override fun onCharacteristicChanged(gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic) {
if (characteristic.uuid == hrCharacteristicUUID) {
val flag = characteristic.properties
val format = if (flag and 0x01 != 0) {
BluetoothGattCharacteristic.FORMAT_UINT16
} else {
BluetoothGattCharacteristic.FORMAT_UINT8
}
val heartRate = characteristic.getIntValue(format, 1) ?: 0
onHeartRateReceived(heartRate)
}
}
Save to HealthKit / Health Connect
On completion record full HKWorkout (iOS) or ExerciseSessionRecord (Android) with all nested metrics: distance, heart rate, route. On iOS — HKWorkoutRouteBuilder for GPS track. User should see workout in system Health or Health Connect.
Timeframes
Basic run tracker with GPS, distance and pace — 3–5 weeks. Full tracker with heart rate, BLE sensors, multiple activity types, GPX export and platform storage integration — 2–4 months.







