DEV Community

linzhongxue
linzhongxue

Posted on

Practical Development of Sports Applications Based on HarmonyOS Next: Building a Basketball Event Management System from Scratch

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);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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);
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

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 } });
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
        });
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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([]);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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...
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Select "File" > "Project Structure" > "Project" > "Signing Configs"
  2. Configure signing certificate information
  3. Check "Automatically generate signing"

2. Build Release Version

  1. Select "Build" > "Build Haps"
  2. Choose "Release" mode
  3. Wait for the build to complete

3. Upload to AppGallery Connect

  1. Log in to the AppGallery Connect console
  2. Select "My Apps" > "Add App"
  3. Fill in app information and upload the built HAP file
  4. 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:

  1. Integrate Huawei Maps services to achieve precise location and navigation for match venues
  2. Add live streaming functionality using Huawei's video services for match broadcasts
  3. Develop a wearable device version to monitor athletes' sports data in real-time
  4. Use Huawei's machine learning services for match data analysis and predictions

Top comments (0)