DEV Community

linzhongxue
linzhongxue

Posted on

Food Discovery App Development Based on HarmonyOS Next

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:

  1. Install DevEco Studio 4.1+ (supports HarmonyOS Next)
  2. Create a project in the AGC console and enable:
    • Authentication Service (phone/email login)
    • Cloud DB (food data storage)
    • Cloud Storage (image management)
  3. 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"  
}  
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

8. Performance Optimization

Enhance app fluidity:

  1. Lazy Image Loading:
// Use LazyForEach instead of ForEach  
LazyForEach(this.foodList,  
  (item: FoodInfo) => {  
    ListItem() {  
      FoodItemView({ food: item })  
    }  
  },  
  item => item.id.toString()  
)  
Enter fullscreen mode Exit fullscreen mode
  1. 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);  
Enter fullscreen mode Exit fullscreen mode
  1. 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  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

9. Project Expansion Directions

  1. Smart Recommendations: Integrate AGC Prediction Service for personalized suggestions
  2. AR Navigation: Implement store navigation using HarmonyOS ARKit
  3. Real-time Reviews: Create instant comment system via AGC Realtime DB
  4. 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)