expo-pedometer
v1.3.0
Published
HealthKit and Health Connect step count module for Expo
Downloads
878
Maintainers
Readme
expo-pedometer
Expo module for reading today's step count from platform health data.
- iOS reads cumulative step count from HealthKit.
- Android reads Health Connect
StepsRecordaggregates. When Health Connect is not available on the device, it falls back to theSensor.TYPE_STEP_COUNTERhardware sensor; the fallback count resets at midnight in the device's time zone. - Web is unavailable and reports denied permissions.
Install
npx expo install expo-pedometerAdd the config plugin:
{
"expo": {
"plugins": [
[
"expo-pedometer",
{
"iosHealthKitPermission": "Allow this app to read your Apple Health step count to show your daily walking progress.",
"iosHealthKitBackgroundDelivery": true,
"androidHealthConnectPrivacyPolicyUrl": "https://example.com/privacy",
"androidHealthConnectBackgroundRead": true
}
]
]
}
}The plugin adds:
- iOS
NSHealthShareUsageDescriptionand HealthKit entitlement. - iOS HealthKit background delivery entitlement when
iosHealthKitBackgroundDeliveryis enabled. - Android
android.permission.health.READ_STEPS. - Android
android.permission.ACTIVITY_RECOGNITIONfor the sensor fallback and background updates on Android 14+. - Android
android.permission.FOREGROUND_SERVICEandandroid.permission.FOREGROUND_SERVICE_HEALTHwhenandroidHealthConnectBackgroundReadis enabled. - Android
android.permission.health.READ_HEALTH_DATA_IN_BACKGROUNDwhenandroidHealthConnectBackgroundReadis enabled. - Android Health Connect package query and permission rationale manifest entries.
AndroidX Health Connect requires Android minSdkVersion 26 or higher. Configure it in your app, for example with expo-build-properties:
[
"expo-build-properties",
{
"android": {
"minSdkVersion": 26
}
}
]API
enum PermissionStatus {
GRANTED = "granted",
UNDETERMINED = "undetermined",
DENIED = "denied",
}
type PermissionResponse = {
status: PermissionStatus;
canAskAgain: boolean;
};
export const isAvailableAsync: () => Promise<boolean>;
export const getPermissionsAsync: () => Promise<PermissionResponse>;
export const requestPermissionsAsync: () => Promise<PermissionResponse>;
export const isBackgroundAvailableAsync: () => Promise<boolean>;
export const getBackgroundPermissionsAsync: () => Promise<PermissionResponse>;
export const requestBackgroundPermissionsAsync: () => Promise<PermissionResponse>;
export const startStepCountUpdatesAsync: (taskName: string) => Promise<void>;
export const stopStepCountUpdatesAsync: (taskName: string) => Promise<void>;
export const getTodayStepCountAsync: () => Promise<number>;
export const usePermissions: () => [
isAvailable: boolean | null,
permission: PermissionResponse | null,
requestPermission: () => Promise<PermissionResponse>,
getPermission: () => Promise<PermissionResponse>,
];
export const useBackgroundPermissions: () => [
isAvailable: boolean | null,
permission: PermissionResponse | null,
requestPermission: () => Promise<PermissionResponse>,
getPermission: () => Promise<PermissionResponse>,
];Example:
import { PermissionStatus, getTodayStepCountAsync, usePermissions } from "expo-pedometer";
import { Button } from "react-native";
export function StepCount() {
const [isAvailable, permission, requestPermission] = usePermissions();
async function requestAndReadSteps() {
const nextPermission = await requestPermission();
if (nextPermission.status === PermissionStatus.GRANTED) {
const steps = await getTodayStepCountAsync();
console.log(steps);
}
}
if (isAvailable === false) {
return null;
}
return (
<Button
title={permission?.status ?? "loading"}
onPress={requestAndReadSteps}
/>
);
}usePermissions() fetches availability and the current permission when the component mounts. Calling
requestPermission() or getPermission() updates the returned permission state.
Background Step Count Task
Install TaskManager before using background updates. Enable iosHealthKitBackgroundDelivery on
iOS or androidHealthConnectBackgroundRead on Android in the config plugin.
npx expo install expo-task-managerDefine the task in global scope so Expo can load it while the app is backgrounded:
import {
PermissionStatus,
requestBackgroundPermissionsAsync,
requestPermissionsAsync,
startStepCountUpdatesAsync,
type StepCountTaskData,
} from "expo-pedometer";
import * as TaskManager from "expo-task-manager";
const STEP_COUNT_TASK = "step-count-updates";
TaskManager.defineTask<StepCountTaskData>(STEP_COUNT_TASK, async ({ data, error }) => {
if (error) {
console.error("Step count task failed", error);
return;
}
console.log("Updated step count", data.steps, new Date(data.observedAt));
// Update app-owned notifications, widgets, or storage here.
});
export async function enableStepCountUpdates() {
const permission = await requestPermissionsAsync();
if (permission.status !== PermissionStatus.GRANTED) {
return;
}
const backgroundPermission = await requestBackgroundPermissionsAsync();
if (backgroundPermission.status === PermissionStatus.GRANTED) {
await startStepCountUpdatesAsync(STEP_COUNT_TASK);
}
}The task receives today's cumulative steps and an observedAt Unix timestamp in milliseconds. Use stopStepCountUpdatesAsync() to stop updates.
HealthKit has no separate background permission. The iOS background permission APIs return granted when HealthKit is available and denied otherwise, while startStepCountUpdatesAsync() controls delivery.
Android Background Step Count Updates
Enable androidHealthConnectBackgroundRead in the config plugin before requesting background
permission. Android background updates use a foreground service. Set
androidForegroundServiceNotificationId to reuse an ongoing notification posted by the app.
[
"expo-pedometer",
{
"androidForegroundServiceNotificationId": 1001
}
]Android Rationale Localization
androidHealthConnectRationaleTitle and androidHealthConnectRationaleDescription are optional overrides. They can be literal strings or Android string resource references.
If the overrides are omitted, the Android rationale screen reads these app string resources:
expo_pedometer_health_connect_rationale_titleexpo_pedometer_health_connect_rationale_description
If those resources are not defined, built-in English defaults are used.
[
"expo-pedometer",
{
"androidHealthConnectRationaleTitle": "Step count access",
"androidHealthConnectRationaleDescription": "Step count is read from Health Connect to show today's walking progress.",
"androidHealthConnectPrivacyPolicyUrl": "@string/privacy_policy_url"
}
]Apps using config plugins can provide localized values by generating those resource names in localized Android resource folders.
Platform Notes
getTodayStepCountAsync() should be called after isAvailableAsync() and a granted permission response.
iOS uses Apple Health's cumulative step count, including sources such as Apple Watch.
HealthKit controls background delivery timing and does not use a fixed polling interval. iOS does not relaunch the app after the user force-quits it.
HealthKit does not report whether a read data type was granted or denied. On iOS, granted means the authorization request completed. If step read access was denied, the count is 0.
Android Health Connect availability depends on device, OS version, and Health Connect installation state. When unavailable, the module uses the step counter sensor when present.
Health data permissions can require store privacy declarations and an app privacy policy before release.
