Step Counting Implementation in Mobile Application
Seemed simplest metric. Actually: iOS and Android count steps differently, phone in pocket vs hand gives different accelerometer patterns, and data duplication between HealthKit and Google Fit — classic user complaint in reviews.
Two Approaches: System Pedometer vs Custom Algorithm
System Pedometer (Recommended)
iOS: CMPedometer — most reliable option. Counts steps at Motion Coprocessor level (M-series), doesn't require app running:
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// Historical data
pedometer.queryPedometerData(from: startDate, to: endDate) { data, error in
guard let data = data else { return }
print("Steps: \(data.numberOfSteps)")
print("Distance: \(data.distance ?? 0) m")
print("Floors up: \(data.floorsAscended ?? 0)")
}
// Live updates
pedometer.startUpdates(from: Date()) { data, error in
DispatchQueue.main.async {
self.stepCount = data?.numberOfSteps.intValue ?? 0
}
}
CMPedometer.startUpdates() continues working even when app background — data accumulates and arrives next open. No battery drain from high-frequency polling — all at hardware level.
Android: TYPE_STEP_COUNTER and TYPE_STEP_DETECTOR.
TYPE_STEP_COUNTER — cumulative counter since last device restart. Resets on reboot. Must store base value on first daily run.
TYPE_STEP_DETECTOR — event per step. For real-time counting.
val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
val stepListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
val totalSteps = event.values[0].toLong()
// Subtract base obtained first launch
val todaySteps = totalSteps - baseStepCount
updateUI(todaySteps)
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
sensorManager.registerListener(stepListener, stepSensor, SensorManager.SENSOR_DELAY_NORMAL)
SENSOR_DELAY_NORMAL — sufficient for pedometer. SENSOR_DELAY_FASTEST pointless here and drains battery.
Custom Algorithm (When Needed)
System pedometer unavailable on some budget Android devices without TYPE_STEP_COUNTER (rare but exists). Then — Peak Detection on accelerometer:
- Read
TYPE_ACCELEROMETERat 25 Hz - Calculate magnitude:
sqrt(x² + y² + z²) - Apply low-pass filter:
filtered = alpha * raw + (1 - alpha) * prev(alpha ≈ 0.1) - Detect peak:
filtered > threshold(usually 10.5–11.5 m/s²) after baseline crossing - Minimum step interval: 250–400 ms
Custom algorithm accuracy — 85–92% vs 98%+ system. For most fitness apps system enough.
HealthKit / Health Connect Integration
Steps must write to platform storage, otherwise don't appear in system Health (iOS) or Health Connect (Android).
iOS — write to HealthKit:
let stepType = HKQuantityType(.stepCount)
let stepSample = HKQuantitySample(
type: stepType,
quantity: HKQuantity(unit: .count(), doubleValue: Double(steps)),
start: periodStart,
end: periodEnd
)
healthStore.save(stepSample) { success, error in }
Android — Health Connect:
val stepsRecord = StepsRecord(
startTime = periodStart,
startZoneOffset = ZoneOffset.UTC,
endTime = periodEnd,
endZoneOffset = ZoneOffset.UTC,
count = steps
)
healthConnectClient.insertRecords(listOf(stepsRecord))
Duplication Problem
If phone sends data to Google Fit and app also writes to Health Connect — user sees double steps in system apps. Solution: don't write steps yourself if reading from system pedometer allowed. Read from system source, aggregate, show in own UI, do NOT write to HealthKit/Health Connect (or write with unique source identifier and warn user of possible duplication).
Timeframes
Step counter with system pedometer on one platform — 2–4 work days. With HealthKit/Health Connect integration, background sync and widget — up to 2 weeks.







