One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Pedometer

Live step count and step history from the device pedometer

One.Pedometer counts steps: a live stream of steps since watching started, plus the past week’s history on iOS. Reach for it for activity rings, walk challenges, and move reminders.

On iOS it uses CMPedometer from Core Motion. On Android it uses the step counter sensor through SensorManager. On web browsers expose no pedometer, so the sensor reads unavailable, watches never fire, history rejects, and permissions read denied.

Usage

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function StepCount() {
const [steps, setSteps] = useState(0)
useEffect(() => {
if (!One.Pedometer.isAvailable()) return
const subscription = One.Pedometer.watchStepCount((result) => {
setSteps(result.steps)
})
return () => subscription.remove()
}, [])
return <Text>{steps} steps</Text>
}

The watch anchors at zero on its first event and counts up from there. The subscription’s remove stops the stream on unmount.

How it works

Step counting needs the motion permission. Set native.app.pedometer.usage, which stamps NSMotionUsageDescription on iOS and the activity-recognition permission on Android. The same iOS usage description gates the barometer. Permissions resolve a status of granted, denied, or undetermined, plus granted and can-ask-again flags.

isAvailable is a synchronous check: step counting hardware on iOS, a step counter sensor on Android. watchStepCount streams { steps } since the watch started on both platforms. getStepCount takes two Date objects and queries the iOS seven-day history; on Android history is unavailable and it rejects with E_PEDOMETER_STEP_COUNT.

API reference

MethodsTypeDescription
isAvailable() => booleanWhether this device counts steps.
() => booleanWhether this device counts steps.
watchStepCount(listener: (result: PedometerResult) => void) => PedometerSubscriptionStream steps since now. First event is zero.
(listener: (result: PedometerResult) => void) => PedometerSubscriptionStream steps since now. First event is zero.
getStepCount(start: Date, end: Date) => Promise<PedometerResult>Steps in a range. iOS history only.
(start: Date, end: Date) => Promise<PedometerResult>Steps in a range. iOS history only.
getPermissions() => Promise<PedometerPermissionResponse>The motion permission, without prompting.
() => Promise<PedometerPermissionResponse>The motion permission, without prompting.
requestPermissions() => Promise<PedometerPermissionResponse>Ask for the motion permission.
() => Promise<PedometerPermissionResponse>Ask for the motion permission.
ResultTypeDescription
stepsnumberSteps since the watch, or in the range.
numberSteps since the watch, or in the range.
PermissionTypeDescription
statusPedometerPermissionStatusGranted, denied, or undetermined.
PedometerPermissionStatusGranted, denied, or undetermined.
grantedbooleanWhether counting may run.
booleanWhether counting may run.
canAskAgainbooleanWhether a prompt may still appear.
booleanWhether a prompt may still appear.
SubscriptionTypeDescription
remove() => voidStop the step stream.
() => voidStop the step stream.

Platform differences

iOSAndroidWeb
EngineCore MotionStep sensorNone
Live stepsYesYesNever fires
HistorySeven daysRejectsRejects
PermissionMotion useActivity useDenied

Limits and gotchas

Examples

Cheer at a step goal

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function StepGoal({ goal }: { goal: number }) {
const [steps, setSteps] = useState(0)
useEffect(() => {
async function watch() {
const permission = await One.Pedometer.requestPermissions()
if (!permission.granted || !One.Pedometer.isAvailable()) return
return One.Pedometer.watchStepCount((result) => {
setSteps(result.steps)
})
}
const subscription = watch()
return () => {
void subscription.then((sub) => sub?.remove())
}
}, [])
return <Text>{steps >= goal ? 'Goal reached!' : `${steps} / ${goal}`}</Text>
}

Permission comes before availability, since a denied grant makes the hardware check moot. The cleanup awaits the pending subscription so an in-flight watch still gets removed.

Show this week’s total on iOS

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
function WeekSteps() {
const [total, setTotal] = useState<number | null>(null)
useEffect(() => {
async function load() {
try {
const week = await One.Pedometer.getStepCount(
new Date(Date.now() - 7 * 86400_000),
new Date()
)
setTotal(week.steps)
} catch {
setTotal(null)
}
}
load()
}, [])
if (total === null) return null
return <Text>{total} steps this week</Text>
}

The rejection path covers Android and denied permission alike, hiding the row where history cannot load instead of showing a zero that lies.

Edit this page on GitHub.