@blife/rn-step-counter
v1.3.0
Published
This library provides an interface for tracking the number of steps taken by the user in a React Native app.
Maintainers
Readme
@blife/rn-step-counter
A React Native TurboModule for live step counting on iOS and Android.
Features
- iOS — Core Motion (
CMPedometer) with floors ascended/descended when available - Android — Hardware step counter (
TYPE_STEP_COUNTER) with accelerometer fallback - New Architecture — TurboModule / Fabric (required)
- Live updates — Event-based API with start/stop lifecycle
- Historical query (iOS) —
queryPedometerDataBetweenDates()for cumulative steps in a date range - Native events — Optional listeners for errors, sensor metadata, and step detection
- Optional filtering —
createStepCountFilter()reduces false positives from rapid motion - Utilities —
parseStepData()for display-friendly formatting
Requirements
| | |
|---|---|
| React Native | >= 0.71.0 |
| Architecture | New Architecture (TurboModules) |
| Expo | Not supported in Expo Go (native module) |
| Autolinking | React Native 0.60+ |
Installation
npm install @blife/rn-step-counteryarn add @blife/rn-step-counter
# or
pnpm add @blife/rn-step-counter
# or
bun add @blife/rn-step-counterRebuild the native app after installing:
# iOS
cd ios && pod install && cd ..
# Android / iOS
npx react-native run-android
npx react-native run-iosPlatform setup
Android
Add permissions and sensor features to your app manifest if they are not already present:
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-feature
android:name="android.hardware.sensor.stepcounter"
android:required="false" />
<uses-feature
android:name="android.hardware.sensor.accelerometer"
android:required="true" />Request runtime permission before starting updates. The library reports permission status via isStepCountingSupported().
iOS
Add a motion usage description to your app's Info.plist:
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to count your steps.</string>Quick start
import { useEffect, useState } from "react";
import {
createStepCountFilter,
isStepCountingSupported,
parseStepData,
startStepCounterUpdate,
stopStepCounterUpdate,
} from "@blife/rn-step-counter";
export function StepCounterScreen() {
const [steps, setSteps] = useState(0);
useEffect(() => {
let active = true;
(async () => {
const { supported, granted } = await isStepCountingSupported();
if (!active || !supported || !granted) {
return;
}
const filter = createStepCountFilter();
startStepCounterUpdate(new Date(), (data) => {
const filtered = filter(data);
if (!filtered) {
return;
}
setSteps(filtered.steps);
});
})();
return () => {
active = false;
stopStepCounterUpdate();
};
}, []);
return null; // render your UI, e.g. parseStepData(...)
}See the full example app: example/src/App.tsx.
API
isStepCountingSupported()
Returns Promise<{ supported: boolean; granted: boolean; working?: boolean }>.
supported— device can count steps (hardware sensor or fallback)granted— required motion/activity permission has been grantedworking— Android only: whether the native sensor service is currently active
queryPedometerDataBetweenDates(start, end) (iOS only)
Queries cumulative step data for the given range using Core Motion. Returns Promise<StepCountData>.
Apple retains roughly seven days of pedometer history; older ranges may return partial data. Does not interfere with an active live session. On Android, throws UnavailabilityError.
import { queryPedometerDataBetweenDates } from "@blife/rn-step-counter";
const start = new Date();
start.setHours(0, 0, 0, 0);
const today = await queryPedometerDataBetweenDates(start, new Date());startStepCounterUpdate(start, callback)
Starts native step updates from start (JavaScript Date) and invokes callback with StepCountData on each event.
On iOS, steps and distance are both cumulative for [start, now], matching queryPedometerDataBetweenDates. A seed query runs immediately so the first event is today's total, not a delta since subscribe.
Returns an EventSubscription. Call stopStepCounterUpdate() to stop.
stopStepCounterUpdate()
Stops the active native session and removes the library's event subscription.
isSensorWorking()
Returns true when this library has an active subscription from startStepCounterUpdate. This reflects the JS-managed session, not Android's native working flag.
Event listeners
Subscribe to native events with the helpers below. Each returns an EventSubscription.
import {
addStepCounterErrorListener,
addStepsSensorInfoListener,
addStepDetectedListener,
addPedometerEventListener,
} from "@blife/rn-step-counter";
const errorSub = addStepCounterErrorListener((error) => {
console.warn(error.message);
});
errorSub.remove();addStepCounterErrorListener—StepCounter.errorOccurred(iOS Core Motion errors)addStepsSensorInfoListener—StepCounter.stepsSensorInfo(capability metadata; fired fromisStepCountingSupportedon iOS and when the Android sensor starts)addStepDetectedListener—StepCounter.stepDetected(native motion-detector signal; not tied to each cumulative step update)addPedometerEventListener—StepCounter.pedometerEvent(iOS pause/resume; requires an activestartStepCounterUpdatesession)
createStepCountFilter(options?)
Returns a stateful filter function. Drops bursts that exceed the configured cadence (default: 250 ms per step) and rebases cumulative counts so ignored steps are not applied later.
A burst is dropped only when both the gap since the last event and the batch window (startDate→endDate) are too short for the new steps. That keeps fused CMPedometer deliveries after the app returns to the foreground. Pass { enabled: false } to skip cadence filtering.
parseStepData(data)
Formats raw StepCountData for display (steps, distance, time range, estimated calories, daily goal progress).
StepCountData
| Field | Type | Description |
|-------|------|-------------|
| steps | number | Cumulative steps for [startDate, endDate] |
| startDate | number | Interval start (Unix ms) |
| endDate | number | Interval end (Unix ms) |
| distance | number | Distance in meters |
| counterType | CounterType | "CMPedometer" · "STEP_COUNTER" · "ACCELEROMETER" |
| floorsAscended | number? | iOS only |
| floorsDescended | number? | iOS only |
| calories | number? | Estimated calories when provided by native |
| currentPaceSecPerM | number? | iOS only — current pace (seconds per meter) |
| averageActivePaceSecPerM | number? | iOS only — average active pace (seconds per meter) |
| currentCadenceStepsPerSec | number? | iOS only — step rate (steps per second) |
Development
bun install
bun run prepare
bun run test
bun run example:start # Metro
bun run example:ios # or example:androidSee CONTRIBUTING.md for the full workflow.
Changelog
See CHANGELOG.md.
