HarmonyOS Sports Development: How to Monitor User Step Data
Preface
When developing sports applications, accurately monitoring and recording user steps is a key feature. HarmonyOS provides a powerful sensor framework that allows developers to easily obtain device motion data. This article will delve into how to implement step monitoring in HarmonyOS applications and share some experiences and tips from the development process to help you better understand and implement this feature.
- Understanding the HarmonyOS Sensor Framework
HarmonyOS offers a variety of sensors, among which the PEDOMETER
(step counter) sensor is the core sensor for obtaining user step data. This sensor returns the cumulative steps since the device was turned on, rather than the incremental steps. This means that we need to handle the initial step value and incremental calculation in the application logic.
- Core Code Implementation
2.1 Initializing the Sensor
Before we begin, we need to request the necessary permissions and initialize the sensor. Here is the code for initializing the sensor:
import { sensor } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import { common } from '@kit.AbilityKit';
export class StepCounterService {
private static instance: StepCounterService;
private stepCount: number = 0; // Current cumulative steps
private initialStepCount: number = 0; // Initial steps (cumulative steps since device startup)
private isMonitoring: boolean = false; // Whether monitoring is ongoing
private isPaused: boolean = false; // Whether paused
private listeners: Array<(steps: number) => void> = [];
private context: common.UIAbilityContext;
private pausedIntervals: Array<PauseCounter> = [];
private deviceTotalSteps: number = 0;
private constructor(context: common.UIAbilityContext) {
this.context = context;
}
public static getInstance(context: common.UIAbilityContext): StepCounterService {
if (!StepCounterService.instance) {
StepCounterService.instance = new StepCounterService(context);
}
return StepCounterService.instance;
}
private async requestMotionPermission(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
try {
const result = await atManager.requestPermissionsFromUser(
this.context,
['ohos.permission.ACTIVITY_MOTION']
);
return result.permissions[0] === 'ohos.permission.ACTIVITY_MOTION' &&
result.authResults[0] === 0;
} catch (err) {
console.error('Permission request failed:', err);
return false;
}
}
public async startStepCounter(): Promise<void> {
if (this.isMonitoring) return;
const hasPermission = await this.requestMotionPermission();
if (!hasPermission) {
throw new Error('Motion sensor permission not granted');
}
try {
sensor.on(sensor.SensorId.PEDOMETER, (data: sensor.PedometerResponse) => {
this.deviceTotalSteps = data.steps;
if (this.initialStepCount === 0) {
this.initialStepCount = data.steps;
}
const deltaSteps = this.deviceTotalSteps - this.initialStepCount;
if (this.isPaused) {
// When paused, only update the device total steps, do not change the business steps
} else {
// Calculate the total steps of all paused intervals
const totalPausedSteps = this.pausedIntervals.reduce((sum, interval) => {
return sum + (interval.end - interval.start);
}, 0);
this.stepCount = deltaSteps - totalPausedSteps;
this.notifyListeners();
}
});
this.isMonitoring = true;
} catch (error) {
const e = error as BusinessError;
console.error(`Step counter subscription failed: Code=${e.code}, Message=${e.message}`);
throw error as Error;
}
}
}
2.2 Pause and Resume Functionality
During the sports activity, users may need to pause and resume the activity. To handle this situation, we need to record the steps when pausing and resuming, and calculate the step increment during the pause period when resuming. Here is the implementation of the pause and resume functionality:
public pauseStepCounter(): void {
if (!this.isMonitoring || this.isPaused) return;
this.pausedIntervals.push({
start: this.deviceTotalSteps,
end: 0
});
this.isPaused = true;
}
public resumeStepCounter(): void {
if (!this.isMonitoring || !this.isPaused) return;
const lastInterval = this.pausedIntervals[this.pausedIntervals.length - 1];
lastInterval.end = this.deviceTotalSteps;
this.isPaused = false;
}
2.3 Stopping the Monitoring
When the user finishes the sports activity, we need to stop monitoring the steps and clean up the related resources:
public stopStepCounter(): void {
if (!this.isMonitoring) return;
this.stepCount = 0;
this.initialStepCount = 0;
this.isPaused = false;
this.pausedIntervals = []; // Clear all pause records
try {
sensor.off(sensor.SensorId.PEDOMETER);
this.isMonitoring = false;
} catch (error) {
const e = error as BusinessError;
console.error(`Step counter unsubscription failed: Code=${e.code}, Message=${e.message}`);
}
}
- Notifying Listeners
To ensure that the interface can update the steps in real-time, we need to notify the listeners when the steps change. To avoid overly frequent notifications, a debounce mechanism can be used:
private debounceTimer?: number;
private notifyListeners(): void {
if (this.debounceTimer) clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
this.listeners.forEach(listener => listener(this.stepCount));
}, 500); // Notify every 500ms
}
- Resetting Steps
In some cases, users may need to reset the steps. Here is the implementation of resetting steps:
public resetStepCounter(): void {
this.initialStepCount = this.deviceTotalSteps;
this.pausedIntervals = [];
this.stepCount = 0;
}
- Usage Example
Here is an example of how to use StepCounterService
in a page:
@Component
export struct KeepRunningPage {
@State isRunning: boolean = false;
@State stepCount: number = 0;
private stepCounterService?: StepCounterService;
build() {
Column() {
Text('Running Step Counter')
.fontSize(24)
.textAlign(TextAlign.Center)
.margin({ top: 20 })
Text(`Steps: ${this.stepCount}`)
.fontSize(18)
.textAlign(TextAlign.Center)
.margin({ top: 20 })
Button('Start Running')
.onClick(() => this.startRunning())
.margin({ top: 20 })
Button('Pause Running')
.onClick(() => this.pauseRunning())
.margin({ top: 20 })
Button('Resume Running')
.onClick(() => this.resumeRunning())
.margin({ top: 20 })
Button('Stop Running')
.onClick(() => this.stopRunning())
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
aboutToAppear(): void {
this.stepCounterService = StepCounterService.getInstance(Application.getInstance().uiAbilityContext);
this.stepCounterService.addStepListener((steps) => {
this.stepCount = steps;
});
}
aboutToDisappear(): void {
this.stepCounterService!.removeStepListener((steps) => {
this.stepCount = steps;
});
}
async startRunning(): Promise<void> {
if (!this.isRunning) {
this.isRunning = true;
await this.stepCounterService!.startStepCounter();
}
}
pauseRunning(): void {
if (this.isRunning) {
this.stepCounterService!.pauseStepCounter();
}
}
resumeRunning(): void {
if (this.isRunning) {
this.stepCounterService!.resumeStepCounter();
}
}
stopRunning(): void {
if (this.isRunning) {
this.isRunning = false;
this.stepCounterService!.stopStepCounter();
}
}
}
- Key Points Summary
6.1 Permission Request
Before using the sensor, the ohos.permission.ACTIVITY_MOTION
permission must be requested. This can be achieved through abilityAccessCtrl.createAtManager
.
6.2 Initial Step Handling
The PEDOMETER
sensor returns the cumulative steps since the device was turned on. Therefore, we need to record the initial steps when we first obtain the data.
6.3 Pause and Resume Mechanism
To handle possible user pauses and resumes, we introduced the pausedIntervals
array to record the start and end steps of each pause. In this way, we can accurately calculate the step increment during the pause period when resuming and deduct this part of the steps from the total steps.
6.4 Debounce Mechanism
To avoid performance issues caused by frequent notifications to the listeners, a debounce mechanism is implemented in the notifyListeners
method. By setting a timer, we ensure that the listeners are notified only once within a certain time interval (e.g., 500ms), reducing unnecessary updates.
6.5 Reset Functionality
In certain scenarios, users may need to reset the step counter. The resetStepCounter
method allows us to set the initial steps to the current total steps of the device and clear all pause records, thereby resetting the step count.
- Development Considerations
7.1 Permission Management
In practical applications, permission requests are an essential aspect. If users deny the permission request, the app should kindly prompt the user and provide an option to reapply for the permission.
7.2 Resource Management
Sensor monitoring is an ongoing process, and improper management can lead to resource leaks. Therefore, when monitoring is no longer needed, it is crucial to call the sensor.off
method to unsubscribe.
7.3 Data Accuracy
Since the PEDOMETER
sensor returns cumulative steps, it is crucial to ensure data accuracy when handling pauses and resumes. Accurate recording and calculation of step increments during pauses and resumes are necessary to avoid data deviations.
- Conclusion
We have thoroughly explored how to implement step monitoring in HarmonyOS applications. From initializing the sensor and requesting permissions to implementing pause, resume, and stop monitoring functionalities. We hope that this content will help you develop sports applications more effectively.
Top comments (0)