DEV Community

linzhongxue
linzhongxue

Posted on

Developing Sports Applications Based on HarmonyOS Next: A Practical Guide to AppGallery Connect Integration

Developing Sports Applications Based on HarmonyOS Next: A Practical Guide to AppGallery Connect Integration

1. Introduction and Project Overview

With the rise of the national fitness trend, the demand for sports applications on mobile devices has grown significantly. HarmonyOS Next, as a next-generation operating system, provides developers with powerful distributed capabilities and a smooth user experience. This tutorial will guide developers in using the ArkTS language and AppGallery Connect services to build a comprehensive sports social application.

Our sample application, "SportConnect," will include the following core features:

  • Recording and analyzing user sports data
  • Sports community interaction
  • Event registration and management
  • Cloud synchronization of health data

2. Environment Setup and Project Creation

First, ensure that the latest version of DevEco Studio and the HarmonyOS SDK are installed. When creating a new project, select the "Application" template, choose ArkTS as the language, and the Stage model.

// Entry file: EntryAbility.ts  
import UIAbility from '@ohos.app.ability.UIAbility';  
import window from '@ohos.window';  

export default class EntryAbility extends UIAbility {  
  onCreate(want, launchParam) {  
    console.info('SportConnect Application onCreate');  
  }  

  onWindowStageCreate(windowStage: window.WindowStage) {  
    // Load the home page when the main window is created  
    windowStage.loadContent('pages/Index', (err) => {  
      if (err.code) {  
        console.error('Failed to load the content. Cause:' + JSON.stringify(err));  
        return;  
      }  
      console.info('Succeeded in loading the content.');  
    });  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

3. Integrating AppGallery Connect Services

3.1 Configuring the AGC Project

  1. Log in to the AppGallery Connect console and create a new project.
  2. Add a HarmonyOS application to the project.
  3. Download the agconnect-services.json configuration file and place it in the project directory.
// Initialize AGC services when the app starts  
import agconnect from '@hw-agconnect/api-ohos';  
import '@hw-agconnect/core-ohos';  

@Entry  
@Component  
struct Index {  
  aboutToAppear() {  
    // Initialize AGC services  
    agconnect.instance().init(this.context);  
    console.info('AGC initialization completed');  
  }  

  build() {  
    Column() {  
      Text('Welcome to SportConnect')  
        .fontSize(30)  
        .margin({ bottom: 20 })  
    }  
    .width('100%')  
    .height('100%')  
    .justifyContent(FlexAlign.Center)  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

3.2 Integrating Authentication Services

Sports applications typically require a user system. We will use AGC's authentication service:

// Authentication module: AuthService.ts  
import { IdentityAuthManager } from '@hw-agconnect/auth-ohos';  

export class AuthService {  
  // Anonymous login  
  static async anonymousLogin(): Promise<void> {  
    try {  
      await IdentityAuthManager.signIn();  
      console.info('Anonymous login success');  
    } catch (err) {  
      console.error(`Login failed: ${JSON.stringify(err)}`);  
    }  
  }  

  // Phone number login  
  static async phoneLogin(phone: string, code: string): Promise<void> {  
    try {  
      const credential = IdentityAuthManager.credentialWithVerifyCode(  
        phone,  
        code,  
        IdentityAuthManager.PHONE_VERIFY_CODE_LOGIN  
      );  
      await IdentityAuthManager.signIn(credential);  
      console.info('Phone login success');  
    } catch (err) {  
      console.error(`Phone login failed: ${JSON.stringify(err)}`);  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

4. Collecting and Storing Sports Data

4.1 Health Data Collection

Using HarmonyOS's health data management API:

// Motion data collection module: MotionService.ts  
import { health, healthKit } from '@kit.HealthKit';  

export class MotionService {  
  // Request health data permissions  
  static async requestPermissions(): Promise<void> {  
    const permissions: Array<string> = [  
      'ohos.permission.health.READ_HEALTH_DATA',  
      'ohos.permission.health.WRITE_HEALTH_DATA'  
    ];  

    try {  
      await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(  
        this.context,  
        permissions  
      );  
      console.info('Health permissions granted');  
    } catch (err) {  
      console.error(`Failed to get health permissions: ${JSON.stringify(err)}`);  
    }  
  }  

  // Get today's step count  
  static async getTodaySteps(): Promise<number> {  
    try {  
      const options = {  
        startTime: new Date(new Date().setHours(0, 0, 0, 0)).getTime(),  
        endTime: new Date().getTime(),  
        dataType: health.DataType.DATA_TYPE_STEP_COUNT  
      };  

      const result = await health.getHealthData(options);  
      return result?.length > 0 ? result[0].value : 0;  
    } catch (err) {  
      console.error(`Get steps failed: ${JSON.stringify(err)}`);  
      return 0;  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

4.2 Cloud Data Storage

Using AGC's Cloud Database to store user sports data:

// Data storage module: CloudDBService.ts  
import { clouddb } from '@hw-agconnect/database-ohos';  

const CLOUDDB_ZONE_NAME = 'SportDataZone';  
const SPORT_RECORD_TYPE = 'SportRecord';  

interface SportRecord {  
  id: string;  
  userId: string;  
  sportType: string;  
  duration: number; // Minutes  
  calories: number;  
  distance?: number; // Kilometers  
  startTime: number;  
  endTime: number;  
}  

export class CloudDBService {  
  private static cloudDB: clouddb.CloudDBZone;  

  // Initialize Cloud Database  
  static async initCloudDB(): Promise<void> {  
    try {  
      const config = new clouddb.CloudDBZoneConfig(  
        CLOUDDB_ZONE_NAME,  
        clouddb.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,  
        clouddb.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC  
      );  

      this.cloudDB = await clouddb.CloudDBZone.open(config);  
      await clouddb.CloudDBZone.registerObjectClass(this.cloudDB, SPORT_RECORD_TYPE);  
      console.info('CloudDB initialized successfully');  
    } catch (err) {  
      console.error(`CloudDB init failed: ${JSON.stringify(err)}`);  
    }  
  }  

  // Add a sports record  
  static async addSportRecord(record: SportRecord): Promise<boolean> {  
    try {  
      await this.cloudDB.executeUpsert(SPORT_RECORD_TYPE, [record]);  
      return true;  
    } catch (err) {  
      console.error(`Add sport record failed: ${JSON.stringify(err)}`);  
      return false;  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

5. Implementing Community Features

5.1 Posting User Updates

// Community module: CommunityService.ts  
import { clouddb } from '@hw-agconnect/database-ohos';  

const POST_TYPE = 'CommunityPost';  

interface CommunityPost {  
  id: string;  
  userId: string;  
  content: string;  
  images?: Array<string>;  
  likes: number;  
  comments: number;  
  createTime: number;  
  sportType?: string;  
}  

export class CommunityService {  
  // Create a post  
  static async createPost(post: CommunityPost): Promise<boolean> {  
    try {  
      await CloudDBService.cloudDB.executeUpsert(POST_TYPE, [post]);  
      return true;  
    } catch (err) {  
      console.error(`Create post failed: ${JSON.stringify(err)}`);  
      return false;  
    }  
  }  

  // Get trending posts  
  static async getHotPosts(limit: number = 10): Promise<Array<CommunityPost>> {  
    try {  
      const query = clouddb.CloudDBZoneQuery.where(POST_TYPE)  
        .orderByDesc('likes')  
        .limit(limit);  

      const result = await CloudDBService.cloudDB.executeQuery(query, POST_TYPE);  
      return result as Array<CommunityPost>;  
    } catch (err) {  
      console.error(`Get posts failed: ${JSON.stringify(err)}`);  
      return [];  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

5.2 Implementing the Post List UI

// Community page: CommunityPage.ets  
@Component  
struct PostItem {  
  @Prop post: CommunityPost  

  build() {  
    Column() {  
      Row() {  
        Image($r('app.media.default_avatar'))  
          .width(40)  
          .height(40)  
          .borderRadius(20)  
          .margin({ right: 10 })  

        Column() {  
          Text(`User ${this.post.userId.substring(0, 6)}`)  
            .fontSize(16)  
            .fontWeight(FontWeight.Bold)  
          Text(new Date(this.post.createTime).toLocaleString())  
            .fontSize(12)  
            .fontColor('#999')  
        }  
      }  
      .width('100%')  
      .justifyContent(FlexAlign.Start)  

      Text(this.post.content)  
        .margin({ top: 10, bottom: 10 })  
        .width('100%')  

      // Like and comment section  
      Row() {  
        Image($r('app.media.ic_like'))  
          .width(20)  
          .height(20)  
          .margin({ right: 5 })  
        Text(this.post.likes.toString())  
          .fontSize(14)  

        Image($r('app.media.ic_comment'))  
          .width(20)  
          .height(20)  
          .margin({ left: 15, right: 5 })  
        Text(this.post.comments.toString())  
          .fontSize(14)  
      }  
      .width('100%')  
      .margin({ top: 10 })  
    }  
    .padding(15)  
    .borderRadius(10)  
    .backgroundColor('#FFF')  
    .margin({ bottom: 10 })  
    .width('100%')  
  }  
}  

@Entry  
@Component  
struct CommunityPage {  
  @State posts: Array<CommunityPost> = []  

  aboutToAppear() {  
    this.loadPosts()  
  }  

  async loadPosts() {  
    this.posts = await CommunityService.getHotPosts()  
  }  

  build() {  
    Column() {  
      List({ space: 10 }) {  
        ForEach(this.posts, (post: CommunityPost) => {  
          ListItem() {  
            PostItem({ post: post })  
          }  
        })  
      }  
      .width('100%')  
      .layoutWeight(1)  
    }  
    .padding(15)  
    .width('100%')  
    .height('100%')  
    .backgroundColor('#F5F5F5')  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

6. Event Management Features

6.1 Event Data Model

// Event module: EventService.ts  
import { clouddb } from '@hw-agconnect/database-ohos';  

const EVENT_TYPE = 'SportEvent';  

interface SportEvent {  
  id: string;  
  title: string;  
  description: string;  
  location: string;  
  startTime: number;  
  endTime: number;  
  maxParticipants: number;  
  currentParticipants: number;  
  coverImage: string;  
  sportType: string;  
  creatorId: string;  
  createTime: number;  
}  

export class EventService {  
  // Create an event  
  static async createEvent(event: SportEvent): Promise<boolean> {  
    try {  
      await CloudDBService.cloudDB.executeUpsert(EVENT_TYPE, [event]);  
      return true;  
    } catch (err) {  
      console.error(`Create event failed: ${JSON.stringify(err)}`);  
      return false;  
    }  
  }  

  // Get upcoming events  
  static async getUpcomingEvents(limit: number = 5): Promise<Array<SportEvent>> {  
    try {  
      const now = new Date().getTime();  
      const query = clouddb.CloudDBZoneQuery.where(EVENT_TYPE)  
        .greaterThan('startTime', now)  
        .orderByAsc('startTime')  
        .limit(limit);  

      const result = await CloudDBService.cloudDB.executeQuery(query, EVENT_TYPE);  
      return result as Array<SportEvent>;  
    } catch (err) {  
      console.error(`Get events failed: ${JSON.stringify(err)}`);  
      return [];  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

6.2 Implementing the Event Details Page

// Event details page: EventDetailPage.ets  
@Entry  
@Component  
struct EventDetailPage {  
  @State event: SportEvent  
  @State isJoined: boolean = false  

  async joinEvent() {  
    if (this.event.currentParticipants >= this.event.maxParticipants) {  
      prompt.showToast({ message: 'Event is full' });  
      return;  
    }  

    this.event.currentParticipants++;  
    const success = await EventService.createEvent(this.event);  
    if (success) {  
      this.isJoined = true;  
      prompt.showToast({ message: 'Registration successful' });  
    } else {  
      prompt.showToast({ message: 'Registration failed, please try again' });  
    }  
  }  

  build() {  
    Column() {  
      Image(this.event.coverImage)  
        .width('100%')  
        .height(200)  
        .objectFit(ImageFit.Cover)  

      Column() {  
        Text(this.event.title)  
          .fontSize(24)  
          .fontWeight(FontWeight.Bold)  
          .margin({ bottom: 10 })  

        Row() {  
          Image($r('app.media.ic_time'))  
            .width(16)  
            .height(16)  
            .margin({ right: 5 })  
          Text(new Date(this.event.startTime).toLocaleString())  
            .fontSize(14)  
        }  
        .margin({ bottom: 5 })  

        Row() {  
          Image($r('app.media.ic_location'))  
            .width(16)  
            .height(16)  
            .margin({ right: 5 })  
          Text(this.event.location)  
            .fontSize(14)  
        }  
        .margin({ bottom: 15 })  

        Text(this.event.description)  
          .fontSize(16)  
          .margin({ bottom: 20 })  

        Row() {  
          Text(`Participants: ${this.event.currentParticipants}/${this.event.maxParticipants}`)  
            .fontSize(14)  
        }  
        .margin({ bottom: 20 })  

        Button(this.isJoined ? 'Registered' : 'Register Now')  
          .width('80%')  
          .enabled(!this.isJoined)  
          .onClick(() => this.joinEvent())  
      }  
      .padding(20)  
    }  
    .width('100%')  
    .height('100%')  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

7. Application Optimization and Release

7.1 Performance Optimization Recommendations

  1. Pagination Loading: Implement pagination for community posts and event lists.
  2. Local Caching: Use Preferences to cache frequently accessed data locally.
  3. Image Compression: Compress images before uploading them to cloud storage.

7.2 Preparing for Release

  1. Complete the application information configuration in the AGC console.
  2. Configure necessary permission declarations.
  3. Generate a signing certificate.
  4. Build the release version.
// Add necessary permission declarations in config.json  
{  
  "module": {  
    "reqPermissions": [  
      {  
        "name": "ohos.permission.health.READ_HEALTH_DATA",  
        "reason": "Read sports and health data"  
      },  
      {  
        "name": "ohos.permission.health.WRITE_HEALTH_DATA",  
        "reason": "Record sports data"  
      },  
      {  
        "name": "ohos.permission.INTERNET",  
        "reason": "Access network services"  
      }  
    ]  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

8. Conclusion

This tutorial provided a detailed guide on developing sports applications using HarmonyOS Next and AppGallery Connect. By integrating AGC's authentication, database, and other services, we implemented core features such as user systems, sports data recording, community interaction, and event management. ArkTS's declarative UI development approach significantly improves development efficiency, while HarmonyOS's distributed capabilities lay the foundation for future multi-device sports experiences.

Developers can further expand on this foundation by:

  • Adding sports trajectory recording
  • Implementing sports data visualization and analysis
  • Developing companion apps for wearable devices
  • Adding an achievement system for sports

We hope this tutorial helps developers quickly master the core technologies of HarmonyOS application development and build more outstanding sports and health applications.

Top comments (0)