Development Guide for Smart Sports Applications Based on HarmonyOS Next
Building Your First Fitness App from Scratch
In recent years, fitness and health applications have become increasingly popular. As a developer, you may want to create an app that tracks users' workout data. Today, I'll guide you through building a fully functional fitness app using HarmonyOS Next and AppGallery Connect.
Preparing the Development Environment
First, we need to set up our development tools. Open DevEco Studio and create a new project:
- Select the "Application" template
- Choose "Phone" as the device type
- Select ArkTS as the language
- Name the project "SportsTracker"
After creating the project, we'll configure the AppGallery Connect services. In the AGC console, create a new project and enable the following services:
- Authentication Service (for user login)
- Cloud Database (to store workout data)
- Cloud Storage (to save user-uploaded workout photos)
Download the agconnect-services.json
file and place it in the entry/src/main/resources
directory.
Building the Basic Interface
Let's start by creating a simple main interface. In the entry/src/main/ets/pages
directory, create a new Index.ets
file:
@Entry
@Component
struct Index {
@State currentTab: string = 'home'
build() {
Column() {
// Top title bar
Row() {
Image($r('app.media.logo'))
.width(40)
.height(40)
.margin(10)
Text('Fitness Tracker')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.backgroundColor('#f5f5f5')
// Content area
TabContent(this.currentTab)
// Bottom navigation bar
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Text('Home Content')
}.tabBar('Home')
TabContent() {
Text('Workout Records')
}.tabBar('Records')
TabContent() {
Text('Profile')
}.tabBar('Me')
}
.barWidth('100%')
.barHeight(60)
}
.width('100%')
.height('100%')
}
}
@Component
struct TabContent {
@Link currentTab: string
build() {
if (this.currentTab === 'home') {
HomePage()
} else if (this.currentTab === 'records') {
RecordsPage()
} else {
ProfilePage()
}
}
}
This basic interface includes a top title bar, content area, and bottom navigation bar. We've used the Tabs
component to implement page switching.
Implementing Workout Data Collection
The core functionality of a fitness app is collecting workout data. HarmonyOS provides rich sensor APIs that make it easy to gather various types of workout data.
Create a new sensor
directory under entry/src/main/ets
, then create a SportsSensor.ts
file:
import sensor from '@ohos.sensor';
// Workout sensor management class
export class SportsSensor {
private stepCounter: number = 0;
private heartRate: number = 0;
private calorie: number = 0;
// Initialize sensors
initSensors() {
try {
// Step counter sensor
sensor.on(sensor.SensorId.STEP_COUNTER, (data) => {
this.stepCounter = data.steps;
console.log(`Current steps: ${this.stepCounter}`);
});
// Heart rate sensor
sensor.on(sensor.SensorId.HEART_RATE, (data) => {
this.heartRate = data.heartRate;
console.log(`Current heart rate: ${this.heartRate}`);
});
// Accelerometer (for calculating calories)
sensor.on(sensor.SensorId.ACCELEROMETER, (data) => {
// Simple calorie calculation
const intensity = Math.sqrt(data.x*data.x + data.y*data.y + data.z*data.z);
this.calorie += intensity * 0.001;
console.log(`Calories burned: ${this.calorie.toFixed(2)}`);
});
} catch (error) {
console.error(`Sensor initialization failed: ${error}`);
}
}
// Get current steps
getSteps(): number {
return this.stepCounter;
}
// Get current heart rate
getHeartRate(): number {
return this.heartRate;
}
// Get calories burned
getCalorie(): number {
return this.calorie;
}
// Stop sensors
stopSensors() {
sensor.off(sensor.SensorId.STEP_COUNTER);
sensor.off(sensor.SensorId.HEART_RATE);
sensor.off(sensor.SensorId.ACCELEROMETER);
}
}
Implementing User Authentication
A user system is an essential component of any app. We can quickly implement user login functionality using AppGallery Connect's authentication service.
Create a service
directory under entry/src/main/ets
, then create an AuthService.ts
file:
import agconnect from '@ohos/agconnect';
import { agc } from '@ohos/agconnect-auth';
export class AuthService {
// User login status
@State isLoggedIn: boolean = false;
// Current user information
@State currentUser: agc.User | null = null;
constructor() {
// Check if a user is already logged in
this.checkLoginStatus();
}
// Check login status
private checkLoginStatus() {
this.currentUser = agconnect.auth().getCurrentUser();
this.isLoggedIn = this.currentUser !== null;
}
// Email login
async loginWithEmail(email: string, password: string): Promise<boolean> {
try {
const user = await agconnect.auth().signInWithEmailAndPassword(email, password);
this.currentUser = user;
this.isLoggedIn = true;
return true;
} catch (error) {
console.error(`Login failed: ${error}`);
return false;
}
}
// Anonymous login
async anonymousLogin(): Promise<boolean> {
try {
const user = await agconnect.auth().signInAnonymously();
this.currentUser = user;
this.isLoggedIn = true;
return true;
} catch (error) {
console.error(`Anonymous login failed: ${error}`);
return false;
}
}
// Register new user
async register(email: string, password: string): Promise<boolean> {
try {
await agconnect.auth().createUserWithEmailAndPassword(email, password);
return true;
} catch (error) {
console.error(`Registration failed: ${error}`);
return false;
}
}
// Logout
async logout(): Promise<void> {
try {
await agconnect.auth().signOut();
this.currentUser = null;
this.isLoggedIn = false;
} catch (error) {
console.error(`Logout failed: ${error}`);
}
}
}
Data Storage and Synchronization
Workout data needs to be stored persistently. We can use AppGallery Connect's cloud database service for this purpose.
Create a DataService.ts
file in the service
directory:
import agconnect from '@ohos/agconnect';
import { cloud } from '@ohos/agconnect-cloud';
export class DataService {
private db = cloud.database();
// Save workout record
async saveWorkoutRecord(record: WorkoutRecord): Promise<boolean> {
try {
const user = agconnect.auth().getCurrentUser();
if (!user) {
console.error('User not logged in');
return false;
}
await this.db.collection('workouts').add({
userId: user.uid,
date: new Date().toISOString(),
steps: record.steps,
heartRate: record.heartRate,
calorie: record.calorie,
duration: record.duration
});
return true;
} catch (error) {
console.error(`Failed to save record: ${error}`);
return false;
}
}
// Get user workout records
async getUserWorkouts(userId: string): Promise<WorkoutRecord[]> {
try {
const result = await this.db.collection('workouts')
.where({ userId: userId })
.orderBy('date', 'desc')
.get();
return result.data.map((doc: any) => ({
id: doc.id,
date: doc.date,
steps: doc.steps,
heartRate: doc.heartRate,
calorie: doc.calorie,
duration: doc.duration
}));
} catch (error) {
console.error(`Failed to get records: ${error}`);
return [];
}
}
}
interface WorkoutRecord {
id?: string;
date: string;
steps: number;
heartRate: number;
calorie: number;
duration: number;
}
Implementing Data Visualization
Data visualization helps users better understand their workout progress. Let's implement a simple chart component.
Create a WorkoutChart.ets
file in the entry/src/main/ets/components
directory:
@Component
export struct WorkoutChart {
@Prop data: {date: string, value: number}[]
@Prop color: string = '#3366ff'
@Prop title: string = ''
build() {
Column() {
// Chart title
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
// Chart container
Row() {
// Y-axis labels
Column() {
Text(Math.max(...this.data.map(d => d.value)).toString())
.fontSize(12)
Text('0')
.fontSize(12)
.margin({ top: '80%' })
}
.width(30)
// Chart body
Stack() {
// Background grid lines
ForEach(Array.from({length: 5}), (_, i) => {
Line()
.width('100%')
.height(1)
.backgroundColor('#eeeeee')
.margin({ top: `${i * 25}%` })
})
// Data line
Path()
.width('100%')
.height('100%')
.commands(this.getPathCommands())
.stroke(this.color)
.strokeWidth(2)
.fillOpacity(0)
}
.height(150)
.width('80%')
// X-axis date labels
Column() {
Text(this.data[0]?.date.split('T')[0] || '')
.fontSize(10)
.margin({ left: 10 })
Text(this.data[this.data.length - 1]?.date.split('T')[0] || '')
.fontSize(10)
.margin({ left: '80%' })
}
.width('100%')
}
.width('100%')
}
.padding(10)
}
// Generate path commands
private getPathCommands(): string {
if (this.data.length === 0) return '';
const maxValue = Math.max(...this.data.map(d => d.value));
const step = 100 / (this.data.length - 1);
let commands = `M0 ${100 - (this.data[0].value / maxValue) * 100}`;
for (let i = 1; i < this.data.length; i++) {
const x = i * step;
const y = 100 - (this.data[i].value / maxValue) * 100;
commands += ` L${x} ${y}`;
}
return commands;
}
}
Integrating All Features
Now, let's integrate all the modules to complete the main page implementation.
Modify the HomePage
component in the Index.ets
file:
@Component
struct HomePage {
private sensorManager = new SportsSensor();
private authService = new AuthService();
private dataService = new DataService();
@State currentSteps: number = 0;
@State currentHeartRate: number = 0;
@State currentCalorie: number = 0;
@State isTracking: boolean = false;
@State workoutHistory: WorkoutRecord[] = [];
aboutToAppear() {
this.loadWorkoutHistory();
}
// Load workout history
private async loadWorkoutHistory() {
const user = this.authService.currentUser;
if (user) {
this.workoutHistory = await this.dataService.getUserWorkouts(user.uid);
}
}
// Start/stop workout tracking
toggleTracking() {
if (this.isTracking) {
this.sensorManager.stopSensors();
// Save current workout record
this.saveWorkoutRecord();
} else {
this.sensorManager.initSensors();
// Update data every second
setInterval(() => {
this.currentSteps = this.sensorManager.getSteps();
this.currentHeartRate = this.sensorManager.getHeartRate();
this.currentCalorie = this.sensorManager.getCalorie();
}, 1000);
}
this.isTracking = !this.isTracking;
}
// Save workout record
private async saveWorkoutRecord() {
const record: WorkoutRecord = {
date: new Date().toISOString(),
steps: this.currentSteps,
heartRate: Math.round(this.currentHeartRate),
calorie: Math.round(this.currentCalorie),
duration: 60 // Assuming 60 minutes of workout
};
const success = await this.dataService.saveWorkoutRecord(record);
if (success) {
console.log('Workout record saved successfully');
this.loadWorkoutHistory();
}
}
build() {
Column() {
// Workout data overview
Row() {
Column() {
Text('Steps')
.fontSize(14)
Text(this.currentSteps.toString())
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.margin(10)
Column() {
Text('Heart Rate')
.fontSize(14)
Text(this.currentHeartRate.toString())
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.margin(10)
Column() {
Text('Calories')
.fontSize(14)
Text(this.currentCalorie.toFixed(0))
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.margin(10)
}
.justifyContent(FlexAlign.SpaceAround)
.width('100%')
.margin({ top: 20 })
// Start/Stop button
Button(this.isTracking ? 'Stop Workout' : 'Start Workout')
.onClick(() => this.toggleTracking())
.width(200)
.margin(20)
// History chart
if (this.workoutHistory.length > 0) {
WorkoutChart({
data: this.workoutHistory.slice(0, 7).map(record => ({
date: record.date,
value: record.steps
})),
title: 'Last 7 Days Step Count',
color: '#4CAF50'
})
}
}
.width('100%')
.height('100%')
}
}
Testing and Publishing
After development, we need thorough testing:
- Test all features using the emulator in DevEco Studio
- Connect real devices to test sensor data collection
- Test data synchronization under different network conditions
- Verify the user login process is smooth
Once testing passes, we can prepare for publishing:
- Create an app in the AppGallery Connect console
- Configure app information, icons, and screenshots
- Build the release version
- Submit for review
Future Enhancement Directions
This basic version can be further improved:
- Add support for more workout types (running, cycling, etc.)
- Implement social features for sharing workout achievements
- Add an achievement system to motivate users
- Enhance data visualization with more chart types
- Support smart wearable devices for multi-device data sync
Conclusion
Through this article, we've completed the development of a fitness and health app based on HarmonyOS Next. From sensor data collection to user authentication, data storage, and visualization, we've covered the main aspects of building a complete application.
HarmonyOS Next and AppGallery Connect provide powerful infrastructure, allowing us to focus on implementing business logic rather than reinventing the wheel. I hope this example helps you quickly get started with HarmonyOS app development. I look forward to seeing more excellent apps from you!
Top comments (0)