This content originally appeared on DEV Community and was authored by linzhongxue
Comprehensive Guide to Developing a Sports Social App on HarmonyOS Next
Building Your First Sports Social App from Scratch
After meeting many like-minded friends at the gym, I realized how great it would be to have a dedicated social platform for fitness enthusiasts. Fortunately, HarmonyOS Next provides powerful development tools and ecosystem support. Today, let’s bring this idea to life together.
Preparation Before Development
First, open DevEco Studio and create a new project. I recommend selecting the “Empty Ability” template, choosing ArkTS as the language, and the Stage model for compatibility mode. This ensures we’re using the latest development paradigm.
// Application entry file EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
// Initialize when the app starts
onCreate() {
console.log('Sports social app is starting up!');
}
// Create main window
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/HomePage', (err) => {
if (err) {
console.error('Error loading page:', err);
return;
}
console.log('Homepage loaded successfully!');
});
}
}
Designing and Implementing the User System
Developing Login and Registration Features
The foundation of any sports social app is its user system. We can use AppGallery Connect’s authentication service to avoid building our own user system from scratch.
First, enable the authentication service in the AGC console, supporting phone number, email, and anonymous login. Then integrate the AGC SDK into the project:
// User service UserService.ts
import agconnect from '@hw-agconnect/api-ohos';
import '@hw-agconnect/auth-ohos';
class UserService {
// Initialize AGC authentication
static init() {
agconnect.instance().init(this.context);
console.log('User service initialized');
}
// Phone number login
static async loginWithPhone(phone: string, code: string) {
try {
const credential = agconnect.auth.PhoneAuthProvider.credentialWithVerifyCode(
phone,
code
);
await agconnect.auth().signIn(credential);
console.log('Phone login successful');
return true;
} catch (error) {
console.error('Login error:', error);
return false;
}
}
// Get current user
static getCurrentUser() {
return agconnect.auth().currentUser;
}
}
export default UserService;
Designing the Profile Page
With login functionality in place, let’s design the profile page to display user fitness data and basic information.
// Profile page ProfilePage.ets
@Component
struct UserAvatar {
@Prop avatarUrl: string = 'common/default_avatar.png'
build() {
Image(this.avatarUrl)
.width(80)
.height(80)
.borderRadius(40)
.margin(10)
}
}
@Entry
@Component
struct ProfilePage {
@State userInfo: {
nickname: string,
level: number,
totalDistance: number
} = {
nickname: 'Fitness Enthusiast',
level: 3,
totalDistance: 156.8
}
build() {
Column() {
// User avatar section
UserAvatar({ avatarUrl: 'common/user_avatar.jpg' })
// Basic info
Text(this.userInfo.nickname)
.fontSize(24)
.fontWeight(FontWeight.Bold)
// Fitness data display
Row() {
Column() {
Text('Lv.' + this.userInfo.level)
.fontSize(18)
Text('Fitness Level')
.fontSize(12)
.fontColor('#666')
}
.margin({ right: 20 })
Column() {
Text(this.userInfo.totalDistance + 'km')
.fontSize(18)
Text('Total Distance')
.fontSize(12)
.fontColor('#666')
}
}
.margin({ top: 15 })
// Edit profile button
Button('Edit Profile')
.width('60%')
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
Implementing Fitness Data Tracking
Integrating Health Data
HarmonyOS provides comprehensive health data interfaces that allow us to directly access user fitness metrics.
// Fitness service SportService.ts
import { health } from '@kit.HealthKit';
class SportService {
// Request health data permissions
static async requestHealthPermission() {
try {
const permissions = [
'ohos.permission.health.READ_HEALTH_DATA',
'ohos.permission.health.WRITE_HEALTH_DATA'
];
await abilityAccessCtrl.requestPermissionsFromUser(permissions);
console.log('Health data permissions granted');
} catch (error) {
console.error('Permission request error:', error);
}
}
// Get today's step count
static async getTodaySteps() {
try {
const now = new Date();
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const result = await health.getHealthData({
startTime: startOfDay.getTime(),
endTime: now.getTime(),
dataType: 'STEP_COUNT'
});
return result.length > 0 ? result[0].value : 0;
} catch (error) {
console.error('Failed to get steps:', error);
return 0;
}
}
}
export default SportService;
Fitness Tracking Interface
Let’s design an attractive interface to display fitness data:
// Fitness record page SportRecordPage.ets
@Component
struct StepCounter {
@Prop currentSteps: number
@Prop targetSteps: number = 10000
build() {
Column() {
// Circular progress bar
Stack() {
Circle({ width: 180, height: 180 })
.strokeWidth(10)
.stroke('#eee')
Circle({ width: 180, height: 180 })
.strokeWidth(10)
.stroke('#ff5a5f')
.sweepAngle(this.currentSteps / this.targetSteps * 360)
}
// Step count display
Text(this.currentSteps.toString())
.fontSize(36)
.margin({ top: 20 })
Text('/ ' + this.targetSteps + ' steps')
.fontSize(16)
.fontColor('#666')
}
.width('100%')
.margin({ top: 30 })
}
}
@Entry
@Component
struct SportRecordPage {
@State steps: number = 0
aboutToAppear() {
SportService.getTodaySteps().then(steps => {
this.steps = steps;
});
}
build() {
Column() {
StepCounter({ currentSteps: this.steps })
// Other fitness metrics
Row() {
Column() {
Text('5.6')
.fontSize(24)
Text('kilometers')
.fontSize(14)
.fontColor('#666')
}
.margin({ right: 30 })
Column() {
Text('420')
.fontSize(24)
Text('calories')
.fontSize(14)
.fontColor('#666')
}
}
.margin({ top: 40 })
// Start workout button
Button('Start Workout Tracking')
.type(ButtonType.Capsule)
.width('80%')
.height(50)
.margin({ top: 50 })
}
.width('100%')
.height('100%')
.padding(20)
}
}
Developing Social Features
Post Creation Functionality
The core of a sports social app is sharing capabilities. Let’s implement post creation:
// Post service PostService.ts
import { clouddb } from '@hw-agconnect/database-ohos';
class PostService {
private static cloudDBZone: clouddb.CloudDBZone;
// Initialize cloud database
static async init() {
const config = new clouddb.CloudDBZoneConfig(
'SportSocialZone',
clouddb.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
clouddb.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC
);
this.cloudDBZone = await clouddb.CloudDBZone.open(config);
await clouddb.CloudDBZone.registerObjectClass(this.cloudDBZone, 'Post');
console.log('Post database initialized');
}
// Create new post
static async createPost(content: string, images?: string[]) {
const post = {
id: generateId(),
userId: UserService.getCurrentUser().uid,
content,
images: images || [],
likes: 0,
comments: 0,
createTime: new Date().getTime()
};
try {
await this.cloudDBZone.executeUpsert('Post', [post]);
console.log('Post published successfully');
return true;
} catch (error) {
console.error('Failed to publish post:', error);
return false;
}
}
}
export default PostService;
Post Feed Display
// Post feed page PostListPage.ets
@Component
struct PostItem {
@Prop post: {
id: string,
userId: string,
content: string,
likes: number,
comments: number
}
@State isLiked: boolean = false
build() {
Column() {
// User info bar
Row() {
UserAvatar({ avatarUrl: 'common/user_avatar.jpg' })
Column() {
Text('Fitness Pro')
.fontSize(16)
Text('2 hours ago')
.fontSize(12)
.fontColor('#999')
}
.margin({ left: 10 })
}
.width('100%')
.justifyContent(FlexAlign.Start)
// Post content
Text(this.post.content)
.fontSize(16)
.margin({ top: 10, bottom: 10 })
.width('100%')
// Interaction area
Row() {
Image(this.isLiked ? 'common/liked.png' : 'common/like.png')
.width(20)
.height(20)
.onClick(() => {
this.isLiked = !this.isLiked;
})
Text(this.post.likes + (this.isLiked ? 1 : 0) + '')
.fontSize(14)
.margin({ left: 5 })
Image('common/comment.png')
.width(20)
.height(20)
.margin({ left: 20 })
Text(this.post.comments + '')
.fontSize(14)
.margin({ left: 5 })
}
.width('100%')
.margin({ top: 10 })
}
.padding(15)
.borderRadius(10)
.backgroundColor('#fff')
.margin({ bottom: 10 })
.width('100%')
}
}
@Entry
@Component
struct PostListPage {
@State posts: any[] = []
aboutToAppear() {
this.loadPosts();
}
async loadPosts() {
// In practice, this should fetch data from cloud database
this.posts = [
{
id: '1',
userId: 'user1',
content: 'Ran 5km this morning, feeling great!',
likes: 12,
comments: 3
},
{
id: '2',
userId: 'user2',
content: 'Day 3 at the gym, keeping it up!',
likes: 8,
comments: 2
}
];
}
build() {
Column() {
List({ space: 10 }) {
ForEach(this.posts, (post) => {
ListItem() {
PostItem({ post: post })
}
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#f5f5f5')
}
}
App Optimization and Release
Performance Optimization Tips
During actual development, I discovered several techniques to improve app fluidity:
- Lazy image loading: Use LazyForEach for images in posts to load them during scrolling
- Data caching: Cache user info and frequently used data locally to reduce network requests
- Component reuse: Encapsulate common components like user avatars separately
// Optimized image loading component
@Component
struct LazyImage {
@Prop src: string
@State loaded: boolean = false
build() {
Stack() {
if (!this.loaded) {
Progress()
.width(50)
.height(50)
}
Image(this.src)
.onComplete(() => {
this.loaded = true;
})
.opacity(this.loaded ? 1 : 0)
}
.width('100%')
.aspectRatio(1)
}
}
Preparing for App Store Submission
After development is complete, we need to:
- Complete app information in the AGC console
- Generate signing certificates
- Build release version
- Submit for review
Remember to configure all required permissions in config.json:
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.health.READ_HEALTH_DATA",
"reason": "For tracking your fitness data"
},
{
"name": "ohos.permission.INTERNET",
"reason": "For connecting to network services"
}
]
}
}
Final Thoughts
Through this project, we’ve fully implemented the main features of a sports social app. The development experience with HarmonyOS Next is truly excellent, particularly the simplicity of ArkTS language and the convenience of AGC services, which greatly improve development efficiency.
There are many potential extensions for this app, such as:
- Adding workout route tracking
- Implementing a friends system
- Developing fitness achievement systems
- Enhancing workout data analysis
I hope this tutorial provides inspiration. If you have any questions, feel free to discuss them in the comments. Looking forward to seeing your sports social app creations!
This content originally appeared on DEV Community and was authored by linzhongxue