Food Discovery App Development Based on HarmonyOS Next
In the mobile app development landscape, food discovery applications consistently hold significant importance. HarmonyOS Next, with its distributed capabilities and seamless performance, offers new possibilities for culinary app development. This article combines AppGallery Connect (AGC) services to guide you step-by-step in building a fully functional food discovery application.
1. Development Environment Setup
First, ensure proper environment configuration:
- Install DevEco Studio 4.1+ (supports HarmonyOS Next)
- Create a project in the AGC console and enable:
- Authentication Service (phone/email login)
- Cloud DB (food data storage)
- Cloud Storage (image management)
- Integrate AGC SDK:
// oh-package.json5
"dependencies": {
"@hw-agconnect/auth-ohos": "^1.9.1",
"@hw-agconnect/clouddb-ohos": "^1.9.1",
"@hw-agconnect/storage-ohos": "^1.9.1"
}
2. User Authentication Module
Implement secure login using AGC Auth:
// src/main/ets/pages/LoginPage.ets
import { AGConnectAuth, AGCAuth } from '@hw-agconnect/auth-ohos';
@Entry
@Component
struct LoginPage {
@State phone: string = ''
@State verifyCode: string = ''
// Send SMS verification code
sendSMSCode() {
AGConnectAuth.getInstance().requestVerifyCode(this.phone, AGCAuth.VerifyCodeAction.LOGIN)
.then(() => { prompt.showToast({ message: 'Verification code sent' }) })
.catch(err => { console.error('Send failed: ' + JSON.stringify(err)) })
}
// Phone verification login
phoneLogin() {
const credential = AGCAuth.PhoneAuthProvider.credentialWithVerifyCode(this.phone, this.verifyCode)
AGConnectAuth.getInstance().signIn(credential)
.then(user => {
prompt.showToast({ message: `Welcome ${user.displayName}` })
router.replaceUrl({ url: 'pages/HomePage' })
})
.catch(err => { console.error('Login failed: ' + JSON.stringify(err)) })
}
build() {
Column() {
TextInput({ placeholder: 'Enter phone number' })
.onChange(val => this.phone = val)
Row() {
TextInput({ placeholder: 'Verification code' })
.onChange(val => this.verifyCode = val)
.layoutWeight(1)
Button('Get Code')
.onClick(() => this.sendSMSCode())
}
Button('Login', { type: ButtonType.Capsule })
.onClick(() => this.phoneLogin())
.margin(20)
}
}
}
3. Cloud Data Structure Design
Create food data model in AGC Cloud DB:
// Define food object type
@Class
export class FoodInfo {
@Field()
id: number = 0; // Unique identifier
@Field({ isIndex: true })
name: string = ''; // Food name
@Field()
category: string = ''; // Category (e.g., Sichuan Cuisine)
@Field()
rating: number = 0; // Rating (0-5)
@Field()
coverUrl: string = ''; // Cover image URL
@Field()
location: string = ''; // Store location
// Constructor
constructor(name?: string, rating?: number) {
this.name = name || '';
this.rating = rating || 0;
}
}
4. Cloud Data Interaction
Implement CRUD operations:
// src/main/ets/utils/CloudDBManager.ets
import { clouddb } from '@hw-agconnect/clouddb-ohos';
export class FoodManager {
private cloudDBZone: clouddb.CloudDBZone | null = null;
// Initialize Cloud DB
async initCloudDB() {
const agcCloudDB = clouddb.CloudDBZoneWrapper.getInstance();
const zoneConfig = new clouddb.CloudDBZoneConfig('FoodZone', clouddb.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE);
this.cloudDBZone = await agcCloudDB.openCloudDBZone(zoneConfig);
}
// Add food data
async addFood(food: FoodInfo): Promise<boolean> {
try {
const result = await this.cloudDBZone?.executeUpsert(food);
return result?.length > 0;
} catch (err) {
console.error('Add failed: ' + JSON.stringify(err));
return false;
}
}
// Query all foods (with pagination)
async queryAllFoods(page: number, pageSize: number): Promise<FoodInfo[]> {
const query = clouddb.CloudDBZoneQuery.where(FoodInfo).limit(pageSize, page * pageSize);
try {
const snapshot = await this.cloudDBZone?.executeQuery(query, clouddb.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
return snapshot?.getSnapshotObjects() || [];
} catch (err) {
console.error('Query failed: ' + JSON.stringify(err));
return [];
}
}
}
5. Image Upload & Display
Manage food images via Cloud Storage:
// src/main/ets/utils/ImageManager.ets
import { agconnect } from '@hw-agconnect/core-ohos';
import { storage } from '@hw-agconnect/storage-ohos';
export class ImageUploader {
// Upload to Cloud Storage
static async uploadImage(fileUri: string): Promise<string> {
try {
const storageManagement = agconnect.storage().storageManagement();
const reference = storageManagement.storageReference(`foods/${new Date().getTime()}.jpg`);
await reference.putFile(fileUri);
return await reference.getDownloadURL();
} catch (err) {
console.error('Upload failed: ' + JSON.stringify(err));
return '';
}
}
// Image display component
@Builder
static FoodImage(url: string) {
if (url) {
Image(url)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
} else {
Image($r('app.media.placeholder'))
.width('100%')
.height(200)
}
}
}
6. Food Discovery Page Implementation
Core functionality integration:
// src/main/ets/pages/HomePage.ets
import { FoodManager } from '../utils/CloudDBManager';
import { ImageUploader } from '../utils/ImageManager';
@Entry
@Component
struct HomePage {
private foodManager = new FoodManager();
@State foodList: FoodInfo[] = []
@State currentPage: number = 0
@State newFood: FoodInfo = new FoodInfo()
// Initialize on page load
aboutToAppear() {
this.foodManager.initCloudDB().then(() => this.loadFoods());
}
// Load food data
loadFoods() {
this.foodManager.queryAllFoods(this.currentPage, 10).then(list => {
this.foodList = [...this.foodList, ...list];
});
}
// Add new food
async addNewFood() {
if (await this.foodManager.addFood(this.newFood)) {
prompt.showToast({ message: 'Added successfully!' });
this.newFood = new FoodInfo();
this.loadFoods();
}
}
build() {
Column() {
// Food list
List({ space: 10 }) {
ForEach(this.foodList, (item: FoodInfo) => {
ListItem() {
Column() {
ImageUploader.FoodImage(item.coverUrl)
Text(item.name).fontSize(18).margin({ top: 5 })
Row() {
ForEach([1, 2, 3, 4, 5], (star) => {
Image($r(star <= item.rating ? 'app.media.star_filled' : 'app.media.star_empty'))
.width(20)
.height(20)
.margin({ right: 2 })
})
}
Text(`Location: ${item.location}`).fontColor(Color.Gray)
}
}
})
}
// Load more button
Button('Load More', { type: ButtonType.Normal })
.onClick(() => {
this.currentPage++;
this.loadFoods();
})
.margin(15)
}
}
}
7. Data Sync & Cross-Device Collaboration
Implement multi-device synchronization:
// Monitor cloud data changes
setupCloudListener() {
const subscribe = clouddb.CloudDBZoneSnapshot.forSubscribe();
const query = clouddb.CloudDBZoneQuery.where(FoodInfo);
this.cloudDBZone?.subscribeSnapshot(query,
clouddb.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY,
(error, snapshot) => {
if (snapshot) {
this.foodList = snapshot.getSnapshotObjects();
}
}
);
}
// Share food across devices
shareToDevice(food: FoodInfo) {
try {
const deviceManager = deviceManager.getDeviceManager();
const devices = deviceManager.getTrustedDeviceListSync();
if (devices.length > 0) {
const params = {
'foodId': food.id.toString(),
'message': `New discovery: ${food.name}`
};
deviceManager.startAbilityByCall({
deviceId: devices[0].deviceId,
bundleName: 'com.example.foodapp',
abilityName: 'DetailAbility',
parameters: params
});
}
} catch (err) {
console.error('Cross-device failed: ' + JSON.stringify(err));
}
}
8. Performance Optimization
Enhance app fluidity:
- Lazy Image Loading:
// Use LazyForEach instead of ForEach
LazyForEach(this.foodList,
(item: FoodInfo) => {
ListItem() {
FoodItemView({ food: item })
}
},
item => item.id.toString()
)
- Data Caching Strategy:
// Hybrid query policy (local cache first)
const policy = clouddb.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_LOCAL_ONLY;
const snapshot = await this.cloudDBZone?.executeQuery(query, policy);
- Rendering Optimization:
@Component
struct FoodItemView {
@Prop food: FoodInfo
build() {
Column() {
// Use @Reusable for component reuse
Image(this.food.coverUrl)
.syncLoad(true) // Prevent flickering
// Use cachedImage to avoid repeated decoding
Text(this.food.name).fontSize(18)
}
.cachedImage(true) // Enable image caching
.reuseId(`food_${this.food.id}`) // Set reuse ID
}
}
9. Project Expansion Directions
- Smart Recommendations: Integrate AGC Prediction Service for personalized suggestions
- AR Navigation: Implement store navigation using HarmonyOS ARKit
- Real-time Reviews: Create instant comment system via AGC Realtime DB
- Health Integration: Connect Huawei Health Kit for dietary suggestions
Through this tutorial, you've mastered core techniques for developing food applications with HarmonyOS Next and AppGallery Connect. From user authentication to distributed collaboration, these technologies apply equally to social, e-commerce, and other application scenarios. HarmonyOS' distributed architecture simplifies multi-device synergy, while AGC's BaaS services significantly reduce backend complexity.
As the HarmonyOS ecosystem evolves, developers gain increasing innovation potential. Recommended focus areas:
- Atomic service development
- Meta service card technology
- Cross-device flow optimization
- New declarative UI paradigms
Mastering these cutting-edge technologies will ensure your applications stand out in the era of seamless connectivity.
Top comments (0)