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.
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.
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.
| Methods | Type | Description |
|---|---|---|
isAvailable() => booleanWhether this device counts steps. | () => boolean | Whether this device counts steps. |
watchStepCount(listener: (result: PedometerResult) => void) => PedometerSubscriptionStream steps since now. First event is zero. | (listener: (result: PedometerResult) => void) => PedometerSubscription | Stream 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. |
| Result | Type | Description |
|---|---|---|
stepsnumberSteps since the watch, or in the range. | number | Steps since the watch, or in the range. |
| Permission | Type | Description |
|---|---|---|
statusPedometerPermissionStatusGranted, denied, or undetermined. | PedometerPermissionStatus | Granted, denied, or undetermined. |
grantedbooleanWhether counting may run. | boolean | Whether counting may run. |
canAskAgainbooleanWhether a prompt may still appear. | boolean | Whether a prompt may still appear. |
| Subscription | Type | Description |
|---|---|---|
remove() => voidStop the step stream. | () => void | Stop the step stream. |
| iOS | Android | Web | |
|---|---|---|---|
| Engine | Core Motion | Step sensor | None |
| Live steps | Yes | Yes | Never fires |
| History | Seven days | Rejects | Rejects |
| Permission | Motion use | Activity use | Denied |
getStepCount. Design the
history UI as an enhancement, not the core.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.
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.