Practical Development of Sports Applications Based on HarmonyOS Next: Building a Basketball Event Management System from Scratch
I. Project Overview and Development Environment Preparation
With the rise of the national fitness trend, the demand for sports applications on mobile devices is growing rapidly. This tutorial will guide developers in using HarmonyOS Next's AppGallery Connect services to develop a fully functional basketball event management system. The system will include core features such as event publishing, team management, and player statistics, comprehensively demonstrating the development process of sports applications in the HarmonyOS ecosystem.
Development Environment Requirements:
- Install the latest version of DevEco Studio (recommended version 4.0 or higher)
- Configure HarmonyOS SDK (API Version 9+)
- Register a Huawei Developer account and enable AppGallery Connect services
Project Technology Stack:
- Frontend: ArkTS + Declarative UI
- Backend: AppGallery Connect's Cloud Database, Cloud Functions, and Authentication Services
- Data Analytics: AppGallery Connect's Analytics Services
II. Project Initialization and Basic Configuration
First, create a new project in DevEco Studio, select the "Application" template, choose "Phone" as the device type, and select ArkTS as the language:
// Project entry file: EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
// Called when the application is created
onCreate(want, launchParam) {
console.info('BasketballApp onCreate');
// Initialize AppGallery Connect services
this.initAGC();
}
private async initAGC() {
try {
// Import AGC core module
const agconnect = await import('@hw-agconnect/core-ohos');
// Initialize AGC with project configuration
agconnect.default.instance().configInstance(
this.context,
{
api_key: "YOUR_API_KEY",
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
project_id: "YOUR_PROJECT_ID",
package_name: "com.example.basketballapp"
}
);
console.info('AGC initialization successful');
} catch (error) {
console.error('AGC initialization failed:', error);
}
}
// Called when the window is created
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err, data) => {
if (err) {
console.error('Failed to load page:', err);
return;
}
console.info('Page loaded successfully:', data);
});
}
}
Configure the application routes in resources/base/profile/main_pages.json
:
{
"src": [
"pages/Index",
"pages/Login",
"pages/Home",
"pages/Matches",
"pages/Teams",
"pages/Players",
"pages/Stats"
]
}
III. User Authentication Module Implementation
Sports applications typically require a user system to manage permissions for different roles. We will use AppGallery Connect's authentication service to implement this functionality.
1. Configure Authentication Service
Enable the authentication service in the AppGallery Connect console and select "Phone Number" and "Email Address" as login methods.
2. Implement Login Page
// pages/Login.ets
import { LoginButton, LoginType } from '@hw-agconnect/auth-ohos';
import router from '@ohos.router';
@Entry
@Component
struct LoginPage {
@State phoneNumber: string = '';
@State verifyCode: string = '';
@State isCounting: boolean = false;
@State countdown: number = 60;
build() {
Column() {
Image($r('app.media.logo'))
.width(120)
.height(120)
.margin({ top: 40, bottom: 30 })
TextInput({ placeholder: 'Enter phone number' })
.type(InputType.Number)
.maxLength(11)
.height(50)
.width('80%')
.onChange((value: string) => {
this.phoneNumber = value;
})
Row() {
TextInput({ placeholder: 'Verification code' })
.type(InputType.Number)
.maxLength(6)
.height(50)
.width('60%')
.onChange((value: string) => {
this.verifyCode = value;
})
Button(this.isCounting ? `Retry in ${this.countdown}s` : 'Get Code')
.width('35%')
.height(50)
.margin({ left: 10 })
.enabled(!this.isCounting && this.phoneNumber.length === 11)
.onClick(() => {
this.sendVerifyCode();
})
}
.width('80%')
.margin({ top: 20 })
Button('Login')
.type(ButtonType.Capsule)
.width('80%')
.height(50)
.margin({ top: 40 })
.enabled(this.phoneNumber.length === 11 && this.verifyCode.length === 6)
.onClick(() => {
this.loginWithPhone();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
}
// Send verification code
private sendVerifyCode() {
const auth = require('@hw-agconnect/auth-ohos').default;
const agconnect = require('@hw-agconnect/core-ohos').default;
auth.getInstance(agconnect.instance())
.requestPhoneVerifyCode(this.phoneNumber)
.then(() => {
console.info('Verification code sent successfully');
this.startCountdown();
})
.catch((err: Error) => {
console.error('Failed to send verification code:', err);
});
}
// Countdown logic
private startCountdown() {
this.isCounting = true;
const timer = setInterval(() => {
if (this.countdown <= 0) {
clearInterval(timer);
this.isCounting = false;
this.countdown = 60;
return;
}
this.countdown--;
}, 1000);
}
// Phone number + verification code login
private loginWithPhone() {
const auth = require('@hw-agconnect/auth-ohos').default;
const agconnect = require('@hw-agconnect/core-ohos').default;
const credential = auth.PhoneAuthProvider.credentialWithVerifyCode(
this.phoneNumber,
'',
this.verifyCode
);
auth.getInstance(agconnect.instance())
.signIn(credential)
.then((user: any) => {
console.info('Login successful:', user);
router.replace({ url: 'pages/Home' });
})
.catch((err: Error) => {
console.error('Login failed:', err);
});
}
}
IV. Event Management Module Development
Event management is the core functionality of sports applications. We will use AppGallery Connect's cloud database to store event data.
1. Create Data Model
Create a matches
collection in the AppGallery Connect console with the following fields:
- matchId: string, primary key
- homeTeam: string, home team name
- awayTeam: string, away team name
- startTime: timestamp, match start time
- location: string, match location
- status: number, match status (0 not started, 1 in progress, 2 ended)
2. Implement Event List Page
// pages/Matches.ets
import { CloudDBZoneWrapper } from '../model/CloudDBZoneWrapper';
import router from '@ohos.router';
@Entry
@Component
struct MatchesPage {
private cloudDBZoneWrapper: CloudDBZoneWrapper = new CloudDBZoneWrapper();
@State matches: Array<any> = [];
@State isLoading: boolean = true;
aboutToAppear() {
this.queryMatches();
}
build() {
Column() {
// Title bar
Row() {
Text('Event Management')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button('+')
.type(ButtonType.Circle)
.width(40)
.height(40)
.onClick(() => {
router.push({ url: 'pages/AddMatch' });
})
}
.width('90%')
.margin({ top: 15, bottom: 15 })
// Loading state
if (this.isLoading) {
LoadingProgress()
.color(Color.Blue)
.margin({ top: 50 })
}
// Event list
List({ space: 10 }) {
ForEach(this.matches, (match: any) => {
ListItem() {
MatchItem({ match: match })
}
}, (match: any) => match.matchId)
}
.width('100%')
.layoutWeight(1)
.divider({ strokeWidth: 1, color: '#F1F1F1' })
}
.width('100%')
.height('100%')
}
// Query event data
private queryMatches() {
this.cloudDBZoneWrapper.queryMatches()
.then((matches: Array<any>) => {
this.matches = matches;
this.isLoading = false;
})
.catch((err: Error) => {
console.error('Failed to query matches:', err);
this.isLoading = false;
});
}
}
// Match item component
@Component
struct MatchItem {
private match: any;
build() {
Row() {
Column() {
Text(`${this.match.homeTeam} vs ${this.match.awayTeam}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`Time: ${new Date(this.match.startTime).toLocaleString()}`)
.fontSize(14)
.margin({ top: 5 })
Text(`Location: ${this.match.location}`)
.fontSize(14)
.margin({ top: 2 })
}
.layoutWeight(1)
// Display different labels based on status
if (this.match.status === 0) {
Text('Not Started')
.fontColor('#666')
} else if (this.match.status === 1) {
Text('In Progress')
.fontColor('#FF9900')
} else {
Text('Ended')
.fontColor('#999')
}
}
.padding(15)
.width('100%')
.onClick(() => {
router.push({ url: 'pages/MatchDetail', params: { matchId: this.match.matchId } });
})
}
}
3. Implement Cloud Database Operations Class
// model/CloudDBZoneWrapper.ts
import { cloud } from '@hw-agconnect/database-ohos';
import agconnect from '@hw-agconnect/core-ohos';
export class CloudDBZoneWrapper {
private cloudDBZone: cloud.CloudDBZone;
constructor() {
// Initialize cloud database zone
const config = {
name: 'BasketballDBZone', // Custom zone name
persistenceEnabled: true, // Enable persistence
encryptionKey: '' // Encryption key (optional)
};
const agcCloudDB = cloud.CloudDBZoneManager.getInstance(agconnect.instance());
this.cloudDBZone = agcCloudDB.openCloudDBZone(config);
}
// Query all matches
queryMatches(): Promise<Array<any>> {
return new Promise((resolve, reject) => {
const query = cloud.CloudDBZoneQuery.where('matches')
.orderByDesc('startTime')
.limit(100);
this.cloudDBZone.executeQuery(query)
.then((snapshot: cloud.CloudDBZoneSnapshot) => {
const matches: Array<any> = [];
while (snapshot.hasNext()) {
matches.push(snapshot.next());
}
resolve(matches);
})
.catch((err: Error) => {
reject(err);
});
});
}
// Add new match
addMatch(match: any): Promise<void> {
return new Promise((resolve, reject) => {
this.cloudDBZone.executeUpsert([match])
.then(() => {
resolve();
})
.catch((err: Error) => {
reject(err);
});
});
}
// Update match status
updateMatchStatus(matchId: string, status: number): Promise<void> {
return new Promise((resolve, reject) => {
const query = cloud.CloudDBZoneQuery.where('matches')
.equalTo('matchId', matchId);
this.cloudDBZone.executeQuery(query)
.then((snapshot: cloud.CloudDBZoneSnapshot) => {
if (snapshot.hasNext()) {
const match = snapshot.next();
match.status = status;
return this.cloudDBZone.executeUpsert([match]);
} else {
throw new Error('Match not found');
}
})
.then(() => {
resolve();
})
.catch((err: Error) => {
reject(err);
});
});
}
}
V. Data Statistics and Analytics
Sports applications need to analyze and statistics match data. We will use AppGallery Connect's analytics services to implement this functionality.
1. Configure Analytics Service
Enable the analytics service in the AppGallery Connect console and create custom events:
- match_view: Match viewing
- stat_record: Data recording
- player_performance: Player performance
2. Implement Data Statistics Functionality
// utils/AnalyticsUtil.ts
import agconnect from '@hw-agconnect/core-ohos';
export class AnalyticsUtil {
private static instance: AnalyticsUtil;
private analytics: any;
private constructor() {
this.analytics = require('@hw-agconnect/analytics-ohos').default;
}
public static getInstance(): AnalyticsUtil {
if (!AnalyticsUtil.instance) {
AnalyticsUtil.instance = new AnalyticsUtil();
}
return AnalyticsUtil.instance;
}
// Record match view event
public logMatchView(matchId: string) {
const eventData = {
match_id: matchId,
timestamp: new Date().getTime()
};
this.analytics.instance(agconnect.instance())
.onEvent('match_view', eventData);
}
// Record match statistics
public logMatchStats(matchId: string, stats: any) {
const eventData = {
match_id: matchId,
...stats
};
this.analytics.instance(agconnect.instance())
.onEvent('stat_record', eventData);
}
// Record player performance
public logPlayerPerformance(playerId: string, performance: any) {
const eventData = {
player_id: playerId,
...performance
};
this.analytics.instance(agconnect.instance())
.onEvent('player_performance', eventData);
}
// Get popular matches data
public getPopularMatches(): Promise<Array<any>> {
return new Promise((resolve, reject) => {
// Here you can call the analytics service API to get data
// In actual development, implementation depends on the specific API of the analytics service
resolve([]);
});
}
}
3. Use Analytics Service in Match Detail Page
// pages/MatchDetail.ets
import { AnalyticsUtil } from '../utils/AnalyticsUtil';
import router from '@ohos.router';
@Entry
@Component
struct MatchDetailPage {
@State match: any = {};
@State isLoading: boolean = true;
aboutToAppear() {
const params = router.getParams() as Record<string, string>;
const matchId = params['matchId'];
if (matchId) {
this.fetchMatchDetail(matchId);
// Record match view event
AnalyticsUtil.getInstance().logMatchView(matchId);
}
}
build() {
Column() {
// Page content...
}
}
private fetchMatchDetail(matchId: string) {
// Logic to get match details...
}
}
VI. Application Packaging and Publishing
After completing development, we need to package the application and publish it to AppGallery.
1. Configure Application Signing
In DevEco Studio:
- Select "File" > "Project Structure" > "Project" > "Signing Configs"
- Configure signing certificate information
- Check "Automatically generate signing"
2. Build Release Version
- Select "Build" > "Build Haps"
- Choose "Release" mode
- Wait for the build to complete
3. Upload to AppGallery Connect
- Log in to the AppGallery Connect console
- Select "My Apps" > "Add App"
- Fill in app information and upload the built HAP file
- Submit for review
VII. Summary and Extensions
Through this tutorial, we have completed the core functionality development of a basketball event management system based on HarmonyOS Next. The system includes typical features of sports applications such as user authentication, event management, and data statistics.
Extension Suggestions:
- Integrate Huawei Maps services to achieve precise location and navigation for match venues
- Add live streaming functionality using Huawei's video services for match broadcasts
- Develop a wearable device version to monitor athletes' sports data in real-time
- Use Huawei's machine learning services for match data analysis and predictions
Top comments (0)