Developing a Mobile App for Runners
Running app market saturated: Nike Run Club, Runkeeper, Adidas Running. New product survives via specialization: corporate running clubs, apps for specific events (marathons, trails), coach platform integration, local communities. Technically, running app development well-studied, but HealthKit/Health Connect and background GPS details regularly raise questions.
Background GPS Tracking: Different Android and iOS Constraints
Android—foreground service with notification—only reliable way to write GPS in background. Android 10+ requires FOREGROUND_SERVICE_TYPE_LOCATION in manifest. Fused Location Provider with PRIORITY_HIGH_ACCURACY and interval = 1000 ms:
class RunTrackingService : Service() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location ->
if (location.accuracy < 20f) { // discard inaccurate points
processLocation(location)
}
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
val request = LocationRequest.Builder(1000L)
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
.setMinUpdateDistanceMeters(3f)
.build()
fusedLocationClient.requestLocationUpdates(request, locationCallback, mainLooper)
return START_STICKY
}
}
On iOS—CLLocationManager with allowsBackgroundLocationUpdates = true and Background Modes → Location updates capability. Important: pausesLocationUpdatesAutomatically = false, else iOS stops tracking if phone stationary for minutes. Critical on slow hill run—iOS thinks user stopped.
HealthKit and Health Connect: Platform Health Integration
On iOS, write run data to HealthKit via HKWorkout:
func saveRun(distance: Double, duration: TimeInterval, route: [CLLocation]) async throws {
let workoutBuilder = HKWorkoutBuilder(healthStore: healthStore,
configuration: HKWorkoutConfiguration().apply {
$0.activityType = .running
$0.locationType = .outdoor
},
device: .local())
try await workoutBuilder.beginCollection(at: Date().addingTimeInterval(-duration))
let distanceSample = HKQuantitySample(
type: HKQuantityType(.distanceWalkingRunning),
quantity: HKQuantity(unit: .meter(), doubleValue: distance),
start: workoutBuilder.startDate!, end: Date()
)
try await workoutBuilder.addSamples([distanceSample])
let workout = try await workoutBuilder.finishWorkout()
// Save route
let routeBuilder = HKWorkoutRouteBuilder(healthStore: healthStore, device: .local())
try await routeBuilder.insertRouteData(route)
try await routeBuilder.finishRoute(with: workout, metadata: nil)
}
On Android—Health Connect API (replaced Google Fit 2023). ExerciseSessionRecord + DistanceRecord + RouteRecord. Requires androidx.health.connect:connect-client and permissions WRITE_EXERCISE, WRITE_DISTANCE, WRITE_EXERCISE_ROUTE.
Pace, Distance, and Auto-Pause
Pace (min/km)—speed derivative: pace = 1000 / speed_ms_in_mps. Average over 5-10 second sliding window, else pace jumps per GPS update.
Auto-pause on stop: if speed <0.5 m/s for 5 seconds—pause. Resume—speed >1.5 m/s. These thresholds must be configurable—hill runners on steep climb may walk.
Pace announcement per km (each km): AVSpeechSynthesizer / TextToSpeech with template "Kilometer {N}, pace {M} minutes {S} seconds, total {T}". Important to announce with locked screen—AVAudioSession.Category.playback (iOS).
Heart Rate Monitor and Running Power
Bluetooth LE Heart Rate Profile (0x180D, 0x2A37)—standard for all monitors (Polar, Wahoo TICKR, Garmin HRM). Parse heart rate measurement frame:
fun parseHeartRateMeasurement(value: ByteArray): Int {
val flags = value[0].toInt()
return if (flags and 0x01 == 0) {
value[1].toInt() and 0xFF // 8-bit value
} else {
(value[1].toInt() and 0xFF) + ((value[2].toInt() and 0xFF) shl 8) // 16-bit
}
}
Running power—niche metric. Stryd Pod (chest sensor, BLE) outputs Running Power via Cycling Power Profile (0x1818). Same GATT profile as bike wattmeters—convenient if app already has CP support.
Developing running app with background GPS, HealthKit/Health Connect, BLE monitor, voice announcements: 7-10 weeks per platform. Cross-platform Flutter: 10-14 weeks. Cost individually quoted.







