Mobile Development 56 min read

Building a Health Diet App with HarmonyOS 6: Declarative UI, State Management & Performance Optimization

A developer shares technical insights from building 'Light Meal Moment' on HarmonyOS 6, covering declarative UI with ArkUI, @ComponentV2 state management, service cards, background tasks, and security — with code examples and measured performance gains of 30-44% across startup, navigation, and memory.

51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
Building a Health Diet App with HarmonyOS 6: Declarative UI, State Management & Performance Optimization

Introduction: From Idea to Implementation

The author noticed friends attempting intermittent fasting without scientific guidance tools. Existing apps were either single-function, poor UX, paywalled, or ad-heavy. Research showed users needed personalized diet plans, scientific fasting guidance, and intuitive data feedback. The developer chose HarmonyOS 6 for its distributed device capabilities, declarative UI (ArkUI), security model, performance improvements, and ecosystem potential.

Technology Selection: Why HarmonyOS 6

Cross-device adaptation : Health data sync across phone, tablet, watch via distributed capabilities

Declarative UI : Familiar from Compose/Flutter; ArkUI enabled fast onboarding

Security & privacy : Granular permission management for sensitive health data

Performance : Rendering and resource management upgrades

Ecosystem outlook : Long-term potential as Huawei's flagship OS

Core Scenarios: HarmonyOS 6 Features Empowering the App

(1) Declarative UI Simplifies Complex Layouts

Challenge : Health dashboard required many metrics; traditional layouts produced verbose, nested code.

Solution : Used ArkUI flex and grid layouts.

Result : Code reduced by 50%; layout clearer and easier to maintain.

(2) Improved State Management Boosts Performance

Challenge : Complex app state, difficult inter-component communication, frequent inconsistencies.

Solution : Adopted @ComponentV2 decorator for state synchronization.

Result : State update response speed improved 30%; smoother UX.

(3) Safeguarding User Data

Challenge : Sensitive health data requires privacy protection with good permission UX.

Solution : Leveraged HarmonyOS 6 granular permission model, requesting permissions on-demand.

Result : Permission grant rate rose from 65% to 85%; user trust increased.

App Overview

Positioning & Core Features

Healthy fasting recommendations : Personalized fasting plans based on body data, habits, goals

Recording & analysis : Quick logging of weight, water, exercise, sleep, bowel movements with scientific advice

Light fasting knowledge base : Tips for fasting, eating, and guides

Target Users

Urban professionals 25-40, health-conscious but time-poor

Weight management seekers preferring science over starvation

Users interested in fasting wanting professional guidance

Technical Architecture

Layered architecture: UI layer (ArkUI declarative), business logic layer (fasting plan generation, nutrition analysis), data access layer (local storage + cloud sync), service layer (health analysis, push notifications). Distributed capabilities enable consistent experience across devices — e.g., plan on phone, detailed analysis on tablet, reminders on watch.

Development Challenges & Solutions

1. Data Security

Health apps handle sensitive data (weight, diet logs, fasting plans). A news story about a health app data leak heightened awareness. Security became the top priority.

2. Performance Optimization

Feature creep caused bloat. Users reported slow startup, janky transitions, especially when loading large history datasets.

Code: Performance Optimizations

(1) Component Lazy Loading

/**
 * Lazy-load component - only loads content when visible
 */
@ComponentV2
struct LazyLoadComponent<T> {
  @Prop item: T;
  @Prop index: number;
  @Prop renderItem: (item: T, index: number) => void;
  @Local isVisible: boolean = false;

  build() {
    View() {
      if (this.isVisible) {
        this.renderItem(this.item, this.index);
      } else {
        // Placeholder to reduce initial render cost
        Text('Loading...')
          .fontSize(14)
          .fontColor($r('app.color.text_secondary'))
          .padding(20)
      }
    }
    .visibility(this.isVisible ? Visibility.Visible : Visibility.Hidden)
    .onAppear(() => {
      // Load actual content when component enters viewport
      this.isVisible = true;
    })
    .onDisappear(() => {
      // Reset state when leaving viewport, release resources
      this.isVisible = false;
    })
  }
}

// Usage example
@ComponentV2
struct HealthRecordsList {
  @Local records: HealthRecord[] = [];

  build() {
    List() {
      ForEach(this.records, (record, index) => {
        LazyLoadComponent({
          item: record,
          index: index,
          renderItem: (item, idx) => {
            // Complex record card rendering
            HealthRecordCard({ record: item });
          }
        });
      });
    }
    .layoutWeight(1)
  }
}

(2) Data Caching & Pagination

/**
 * Data cache service - optimizes frequent data requests
 */
export class DataCacheService {
  private static instance: DataCacheService;
  private cache: Map<string, { data: any; timestamp: number; ttl: number }> = new Map();
  private readonly DEFAULT_TTL: number = 5 * 60 * 1000; // 5 min default

  private constructor() {}

  static getInstance(): DataCacheService {
    if (!DataCacheService.instance) {
      DataCacheService.instance = new DataCacheService();
    }
    return DataCacheService.instance;
  }

  get(key: string): any | null {
    const cached = this.cache.get(key);
    if (cached) {
      const now = Date.now();
      if (now - cached.timestamp < cached.ttl) {
        return cached.data;
      } else {
        this.cache.delete(key);
      }
    }
    return null;
  }

  set(key: string, data: any, ttl: number = this.DEFAULT_TTL): void {
    this.cache.set(key, { data, timestamp: Date.now(), ttl });
  }
}

// Usage - paginated health records
export class HealthRecordService {
  private dataCacheService: DataCacheService = DataCacheService.getInstance();

  async getHealthRecords(page: number = 1, pageSize: number = 20): Promise<HealthRecord[]> {
    const cacheKey = `health_records_${page}_${pageSize}`;
    const cachedData = this.dataCacheService.get(cacheKey);
    if (cachedData) {
      return cachedData;
    }
    const records = await this.healthRecordDao.queryRecords(page, pageSize);
    this.dataCacheService.set(cacheKey, records);
    return records;
  }
}

(3) List Performance Optimization

// Before: new component created on every scroll
List() {
  ForEach(this.records, (record) => {
    HealthRecordCard({ record: record });
  });
}

// After: stableId + reuse strategy
List() {
  ForEach(
    this.records,
    (record) => {
      HealthRecordCard({ record: record });
    },
    (record) => record.id // stable ID helps List reuse items
  );
}
.size(100, 100) // fixed size reduces layout calculation
.edgeEffect(EdgeEffect.None) // remove edge effect, reduce draw overhead
.shadow(false) // disable shadow, improve scroll smoothness

3. Complex Layout Implementation

Home page needed many metrics; traditional layouts made code lengthy and hard to maintain. The TimerPage uses Stack, Grid, Column/Row flex layouts.

Code: TimerPage Complex Layout

/**
 * Timer page - core feature page
 * Uses Stack, Grid, Column/Row flex layouts
 */
@Entry
@ComponentV2
struct TimerPage {
  // State management
  @Local currentMode: FastingMode = Constants.PRESET_FASTING_MODES[0];
  @Local fastingStatus: FastingStatus = FastingStatus.NOT_STARTED;
  @Local startTime: number = 0;
  @Local remainingTime: number = 0;
  @Local progress: number = 0;
  @Local animatedProgress: number = 0;
  @Local countdownText: string = '00:00:00';
  @Local elapsedText: string = '00:00:00';
  @Local showElapsedTime: boolean = false;
  // Services...

  build() {
    Column() {
      this.buildStatusBar()
      Scroll() {
        Column() {
          if (this.isRefreshing) {
            this.buildRefreshIndicator()
          }
          Column() {
            this.buildFastingWidget()
            this.buildFastingTimeModule()
            this.buildControlButtons()
          }.margin({ left: 15, right: 15 })
          Column({ space: 24 }) {
            this.buildWeightManagementModule()
            this.buildHealthManagementModule()
            this.buildQdsCard()
          }.backgroundColor($r('app.color.background_light')).borderRadius(30)
        }
        .width('100%')
        .layoutWeight(1)
        .scrollBar(BarState.Off)
        .onScrollEdge((side: Edge) => {
          if (side === Edge.Top && !this.isRefreshing) {
            this.handlePullToRefresh();
          }
        })
      }
      .width('100%')
      .height('100%')
    }
  }

  /** Central fasting timer with Stack layout */
  @Builder
  buildFastingWidget() {
    Stack() {
      Circle().width(240).height(240).fill('#FFFFFF')
      Circle().width(220).height(220).fill(this.getMainColor())
        .animation({ duration: 500, curve: Curve.EaseOut })
      Circle().width(190).height(190).fill('#FFFFFF')
      Canvas(this.canvasContext).width(290).height(290)
        .onReady(() => this.drawProgress())
      Circle().width(290).height(290).fill('#FFFFFF')
        .opacity(this.rippleOpacity).scale({ x: this.pressScale, y: this.pressScale })
        .position({ x: this.rippleX - 145, y: this.rippleY - 145 })
        .animation({ duration: 300, curve: Curve.EaseOut })
      Column() {
        Text(this.showElapsedTime ? this.elapsedText : this.countdownText)
          .fontSize(32).fontColor(this.getTextColor())
          .animation({ duration: 300, curve: Curve.EaseOut })
        Text(this.getStatusText())
          .fontSize(16).fontColor('#555555')
          .animation({ duration: 300, curve: Curve.EaseOut })
        Text(`Completed: ${Math.round(this.progress * 100)}%`)
          .fontSize(18).margin({top:10}).fontColor(this.getTextColor())
          .animation({ duration: 300, curve: Curve.EaseOut })
      }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)
    }.width(290).height(290)
    .gesture(GestureGroup(GestureMode.Parallel,
      TapGesture({ count: 2 }).onAction(() => this.handleDoubleTap()),
      LongPressGesture().onAction(() => this.handleLongPress()),
      PanGesture()
        .onActionStart((e) => this.handleTouchStart(e))
        .onActionUpdate((e) => this.handleTouchMove(e))
        .onActionEnd((e) => this.handleTouchEnd(e))
    ))
  }

  /** Weight management module with Column/Row + ForEach */
  @Builder
  buildWeightManagementModule() {
    Column({ space: 16 }) {
      Row() {
        Text('Weight Management').fontSize(18).fontColor($r('app.color.text_primary')).fontWeight(FontWeight.Bold)
        Blank()
        Button('+ Add Weight').type(ButtonType.Capsule).backgroundColor('#01B662').fontColor('#FFFFFF').fontSize(14)
          .onClick(() => this.showAddWeightDialog())
      }.width('100%')
      Row() {
        Row() {
          Text('Initial Weight').fontSize(15).fontColor($r('app.color.text_tertiary'))
          Text(`${this.currentWeight > 0 ? this.currentWeight.toFixed(1) : '--'}${this.weightUnit}`)
            .fontSize(15).margin({ left: 10 }).fontColor($r('app.color.text_primary')).fontWeight(FontWeight.Bold)
        }.layoutWeight(1).justifyContent(FlexAlign.Center)
        Row() {
          Text('Target Weight').fontSize(15).fontColor($r('app.color.text_tertiary'))
          Text(`${this.targetWeight > 0 ? this.targetWeight.toFixed(1) : '--'}${this.weightUnit}`)
            .fontSize(15).margin({ left: 10 }).fontColor($r('app.color.text_primary')).fontWeight(FontWeight.Bold)
        }.layoutWeight(1).justifyContent(FlexAlign.Center)
      }.width('100%').height(50).backgroundColor($r('app.color.background_light')).borderRadius(12).padding({ left: 16, right: 16 }).margin({ bottom: 15 })
      this.buildWeeklyWeightChart()
    }.width('100%').borderRadius(30).padding({ left: 16, right: 16, top: 20 }).backgroundColor($r('app.color.background'))
  }

  /** Weekly weight chart with dynamic layout & animation */
  @Builder
  buildWeeklyWeightChart() {
    Column() {
      Row() {
        Text(this.getWeeklyDateRange()).fontSize(14).fontColor('app.color.text_primary')
        Blank()
        Text('Weight Data').fontSize(14).fontColor($r('app.color.text_secondary'))
          .onClick(() => {
            const router = this.getUIContext()?.getRouter();
            router?.pushUrl({ url: 'pages/WeightPage' });
          })
        Image($r('app.media.icon_arrow_right')).width(6).height(10).margin({ left: 5 })
      }.width('100%').padding({ bottom: 8 })
      Row({ space: 8 }) {
        ForEach(this.weeklyWeightData, (data: WeeklyWeightData, index) => {
          Column() {
            Column() {
              Image(this.getWeightIcon(data, index)).width(20).height(20).margin({ bottom: 5 })
              Text(data?.weight !== undefined ? `${data.weight.toFixed(2)}` : '--')
                .fontSize(13).fontColor(data?.weight !== undefined ? $r('app.color.primary') : $r('app.color.text_tertiary')).margin({ bottom: 5 })
            }
            Column() {
              Column().width(20).height(this.getBarHeight(data?.weight, index))
                .linearGradient({ angle: 180, colors: this.getBarGradientColors(data?.weight !== undefined, index), repeating: false })
                .borderRadius(4).scale({ x: this.getBarScale(index), y: this.getBarScale(index) })
                .opacity(this.getBarOpacity(index))
                .shadow({ radius: this.getBarShadowRadius(index), color: this.getBarShadowColor(data?.weight !== undefined, index), offsetX: 0, offsetY: 2 })
                .animation({ duration: 800, curve: Curve.EaseOut, delay: index * 100 })
                .onClick(() => this.handleBarClick(data, index))
                .onHover((isHover) => this.handleBarHover(index, isHover))
            }.height(113).justifyContent(FlexAlign.End)
            Text(this.getWeekDayLabel(index)).fontSize(10).fontColor('#999999').margin({ top: 4 })
          }.layoutWeight(1).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.End)
        })
      }.alignItems(VerticalAlign.Bottom).width('100%').padding({ top: 15, bottom: 15 })
    }.width('100%')
  }

  /** Health management module with independent components */
  @Builder
  buildHealthManagementModule() {
    Column({ space: 16 }) {
      Row() { Text('Health Management').fontSize(18).fontWeight(700).fontColor($r('app.color.text_primary')) }.width('100%').margin({ bottom: 8 })
      WaterManagementItem()
      ExerciseManagementItem()
      EarlyRiseManagementItem()
      EarlySleepManagementItem()
      ToiletManagementItem()
    }.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 12 }).backgroundColor($r('app.color.background')).borderRadius(30)
  }

  /** Quick start card with nested layout & gradients */
  @Builder
  buildQdsCard() {
    Column({ space: 15 }) {
      Row() {
        Image($r('app.media.icon_tips_left')).width(25).height(49)
        Column({ space: 4 }) {
          Text('Easiest Weight Loss').fontSize(18).fontWeight(FontWeight.Bold).fontColor($r('app.color.text_primary'))
          Text('Change Starts Here').fontSize(14).fontColor($r('app.color.text_secondary'))
        }.margin({ left: 10, right: 10 }).alignItems(HorizontalAlign.Center)
        Image($r('app.media.icon_tips_right')).width(25).height(49)
      }.width('100%').justifyContent(FlexAlign.Center)
      Column({ space: 12 }) {
        this.buildTipCard($r('app.media.ic_fast_title1'), 'Fasting Principle', 'Intermittent fasting...', '#CCF0E0')
        this.buildTipCard($r('app.media.ic_fast_title2'), 'Re-feeding Advice', 'Start with light food...', '#FFE6BE')
        this.buildTipCard($r('app.media.ic_fast_title3'), 'Precautions', 'Drink enough water...', '#FFDBE0')
      }.width('100%').padding({ left: 15, right: 15, bottom: 15 })
    }.width('100%').padding({ top: 25, bottom: 15 }).backgroundColor($r('app.color.background')).borderRadius(20)
  }
}

Custom Circular Progress Layout Component

/** Custom circular progress layout */
@ComponentV2
struct CircularProgressLayout {
  @Prop value: number;
  @Prop maxValue: number = 100;
  @Prop color: ResourceColor = $r('app.color.primary');
  @Prop title: string;
  @Prop icon: string;

  build() {
    Stack() {
      Circle().width(120).height(120).fill(Color.Transparent).stroke({ width: 12, color: $r('app.color.divider') })
      Circle().width(120).height(120).fill(Color.Transparent)
        .stroke({ width: 12, color: this.color, strokeDashArray: [2 * Math.PI * 54 * (this.value / this.maxValue), 2 * Math.PI * 54] })
        .rotation(-90)
      Column() {
        Text(this.icon).fontSize(32)
        Text(this.title).fontSize(14).fontColor($r('app.color.text_secondary'))
        Text(`${this.value}/${this.maxValue}`).fontSize(24).fontWeight(FontWeight.Bold).fontColor(this.color)
      }
    }.width(150).height(150).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)
  }
}

// Usage
@ComponentV2
struct CircularProgressDemo {
  build() {
    Row({ space: 24 }) {
      CircularProgressLayout({ value: 75, title: 'Fasting Progress', icon: '⏱️', color: $r('app.color.primary') })
      CircularProgressLayout({ value: 1500, maxValue: 2000, title: 'Water Progress', icon: '💧', color: $r('app.color.secondary') })
    }.padding(24).justifyContent(FlexAlign.Center)
  }
}

4. State Management Chaos

Many states (user data, health metrics, fasting status) caused sync issues as components grew. Initial simple state management led to inconsistencies.

Code: State Management Solutions

(1) @ComponentV2 State Optimization

// Before: traditional state, complex inter-component communication
@Entry
@Component
struct OldDashboardPage {
  @State isLoading: boolean = true;
  @State healthScore: number = 0;
  @State fastingProgress: number = 0;
  @State waterIntake: number = 0;
  // more state...
  build() { /* complex layout & event handling */ }
}

// After: @ComponentV2 + @Local
@Entry
@ComponentV2
struct NewDashboardPage {
  private userProfileService: UserProfileService = UserProfileService.getInstance();
  private healthDataService: HealthDataService = HealthDataService.getInstance();

  @Local isLoading: boolean = true;
  @Local selectedTimeRange: TimeRange = TimeRange.WEEK;
  @Local healthScore: number = 0;
  @Local fastingProgress: number = 0;
  @Local waterIntake: number = 0;
  @Local exerciseMinutes: number = 0;

  aboutToAppear(): void { this.loadHealthData(); }

  private async loadHealthData(): Promise<void> {
    this.isLoading = true;
    try {
      const [healthScore, fastingData, waterData, exerciseData] = await Promise.all([
        this.healthDataService.getHealthScore(this.selectedTimeRange),
        this.healthDataService.getFastingProgress(),
        this.healthDataService.getWaterIntake(),
        this.healthDataService.getExerciseMinutes()
      ]);
      this.healthScore = healthScore;
      this.fastingProgress = fastingData.progress;
      this.waterIntake = waterData.total;
      this.exerciseMinutes = exerciseData.total;
    } catch (error) {
      hilog.error(0x0001, 'DashboardPage', `Failed to load health data: ${JSON.stringify(error)}`);
    } finally {
      this.isLoading = false;
    }
  }

  private onMetricChange(metric: HealthMetricType): void {
    this.selectedMetric = metric;
  }

  build() {
    Column() {
      TimeRangeSelector({ selectedRange: this.selectedTimeRange, onRangeChange: (range) => { this.selectedTimeRange = range; this.loadHealthData(); } });
      HealthMetricCard({ score: this.healthScore, fastingProgress: this.fastingProgress, waterIntake: this.waterIntake, exerciseMinutes: this.exerciseMinutes });
    }.width('100%').height('100%')
  }
}

(2) Global State Management Service (Singleton)

/** Global state management service - singleton */
export class AppStateService {
  private static instance: AppStateService;
  private userProfile: UserProfile | null = null;
  private isLoggedIn: boolean = false;
  private themeMode: ThemeMode = ThemeMode.LIGHT;
  private language: string = 'zh-CN';
  private listeners: Map<string, Array<() => void>> = new Map();

  private constructor() { this.loadSavedState(); }

  static getInstance(): AppStateService {
    if (!AppStateService.instance) { AppStateService.instance = new AppStateService(); }
    return AppStateService.instance;
  }

  private loadSavedState(): void {
    const savedTheme = PreferencesUtil.get('themeMode', ThemeMode.LIGHT);
    this.themeMode = savedTheme;
    const savedLanguage = PreferencesUtil.get('language', 'zh-CN');
    this.language = savedLanguage;
  }

  updateUserProfile(profile: UserProfile): void {
    this.userProfile = profile; this.isLoggedIn = true; this.notifyListeners('userProfile');
  }
  updateThemeMode(theme: ThemeMode): void {
    this.themeMode = theme; PreferencesUtil.set('themeMode', theme); this.notifyListeners('themeMode');
  }
  getThemeMode(): ThemeMode { return this.themeMode; }
  getUserProfile(): UserProfile | null { return this.userProfile; }
  isUserLoggedIn(): boolean { return this.isLoggedIn; }

  registerListener(key: string, callback: () => void): void {
    if (!this.listeners.has(key)) { this.listeners.set(key, []); }
    this.listeners.get(key)?.push(callback);
  }
  unregisterListener(key: string, callback: () => void): void {
    if (this.listeners.has(key)) {
      const callbacks = this.listeners.get(key)?.filter(cb => cb !== callback);
      if (callbacks && callbacks.length > 0) { this.listeners.set(key, callbacks); } else { this.listeners.delete(key); }
    }
  }
  private notifyListeners(key: string): void {
    if (this.listeners.has(key)) {
      this.listeners.get(key)?.forEach(callback => {
        try { callback(); } catch (error) { hilog.error(0x0001, 'AppStateService', `Listener callback error for ${key}: ${JSON.stringify(error)}`); }
      });
    }
  }
}

// Usage
@ComponentV2
struct SettingsPage {
  private appStateService: AppStateService = AppStateService.getInstance();
  @Local themeMode: ThemeMode = this.appStateService.getThemeMode();

  aboutToAppear(): void {
    this.appStateService.registerListener('themeMode', () => { this.themeMode = this.appStateService.getThemeMode(); });
  }
  aboutToDisappear(): void {
    this.appStateService.unregisterListener('themeMode', () => { this.themeMode = this.appStateService.getThemeMode(); });
  }
  private onThemeChange(theme: ThemeMode): void { this.appStateService.updateThemeMode(theme); }
}

(3) Event Bus for Inter-Component Communication

/** Event bus - component communication */
export class EventBus {
  private static instance: EventBus;
  private eventListeners: Map<string, Array<(data?: any) => void>> = new Map();
  private constructor() {}
  static getInstance(): EventBus {
    if (!EventBus.instance) { EventBus.instance = new EventBus(); }
    return EventBus.instance;
  }
  subscribe(eventName: string, callback: (data?: any) => void): void {
    if (!this.eventListeners.has(eventName)) { this.eventListeners.set(eventName, []); }
    this.eventListeners.get(eventName)?.push(callback);
  }
  publish(eventName: string, data?: any): void {
    if (this.eventListeners.has(eventName)) {
      this.eventListeners.get(eventName)?.forEach(callback => {
        try { callback(data); } catch (error) { hilog.error(0x0001, 'EventBus', `Event callback error for ${eventName}: ${JSON.stringify(error)}`); }
      });
    }
  }
  unsubscribe(eventName: string, callback: (data?: any) => void): void {
    if (this.eventListeners.has(eventName)) {
      const listeners = this.eventListeners.get(eventName)?.filter(l => l !== callback);
      if (listeners && listeners.length > 0) { this.eventListeners.set(eventName, listeners); } else { this.eventListeners.delete(eventName); }
    }
  }
  unsubscribeAll(eventName?: string): void {
    if (eventName) { this.eventListeners.delete(eventName); } else { this.eventListeners.clear(); }
  }
}

export enum AppEvents {
  USER_LOGGED_IN = 'user_logged_in',
  USER_LOGGED_OUT = 'user_logged_out',
  HEALTH_DATA_UPDATED = 'health_data_updated',
  THEME_CHANGED = 'theme_changed',
  LANGUAGE_CHANGED = 'language_changed'
}

// Usage
@ComponentV2
struct HealthRecordForm {
  private eventBus: EventBus = EventBus.getInstance();
  private async submitRecord(record: HealthRecord): Promise<void> {
    try {
      await this.healthRecordService.saveRecord(record);
      this.eventBus.publish(AppEvents.HEALTH_DATA_UPDATED);
    } catch (error) { /* handle error */ }
  }
}

@ComponentV2
struct HealthDashboard {
  private eventBus: EventBus = EventBus.getInstance();
  @Local healthData: HealthData = this.initialHealthData;
  aboutToAppear(): void {
    this.eventBus.subscribe(AppEvents.HEALTH_DATA_UPDATED, () => { this.loadHealthData(); });
  }
  aboutToDisappear(): void {
    this.eventBus.unsubscribe(AppEvents.HEALTH_DATA_UPDATED, () => { this.loadHealthData(); });
  }
  private async loadHealthData(): Promise<void> { /* load from service */ }
}

HarmonyOS 6 New Features in Practice

1. Declarative UI Enhancements: ArkUI New Components & APIs

Problem : Health dashboard core page with many metrics. Traditional implementation: 300+ lines, 5 nesting levels, fragile to modify.

New Feature : Enhanced flex/grid layouts, responsive design support.

Code: Health Dashboard Metrics Overview

// Build health metrics overview
@Builder
private buildHealthMetricsOverview() {
  Row() {
    this.buildMetricItem('Fasting', this.fastingStats.completionRate, '%')
    this.buildMetricItem('Weight', this.weightStats.consistencyScore, 'pts')
    this.buildMetricItem('Exercise', this.exerciseStats.completionRate, '%')
    this.buildMetricItem('Water', this.waterStats.completionRate, '%')
  }.width('100%').justifyContent(FlexAlign.SpaceBetween)
}

// Single metric item
@Builder
private buildMetricItem(label: string, value: number, unit: string) {
  Column() {
    Text(label).fontSize(12).fontColor($r('app.color.text_secondary'))
    Text(`${value}${unit}`).fontSize(16).fontColor($r('app.color.text_primary')).fontWeight(FontWeight.Medium)
  }.alignItems(HorizontalAlign.Center)
}

Result : Code halved, layout logic clearer. Responsive design auto-adapts to different device sizes. Development time for layouts dropped from one day to half a day.

2. Advanced State Management: @ComponentV2

Problem : Many synced states (e.g., user modifies fasting plan → update dashboard, records, stats). Event bus became unwieldy; ordering bugs caused stale UI.

New Feature : Optimized state management, @State performance improvements, simpler inter-component communication.

Code: Health Data State Management

// Demonstrates decorator differences; imports & types unchanged
@ComponentV2
export struct DashboardPage {
  private userProfileService: UserProfileService = UserProfileService.getInstance();
  private healthDataService: HealthAnalysisService = HealthAnalysisService.getInstance();
  private themeService: ThemeService = ThemeService.getInstance();

  @Local isLoading: boolean = true;
  @Local overallHealthScore: number = 0;
  @Local healthTrend: HealthTrend = HealthTrend.STABLE;
  @Local currentTimeRange: TimeRange = TimeRange.WEEK;
  @Local selectedMetric: HealthMetricType = HealthMetricType.OVERALL;
  @Local fastingStats: HealthStats = { completionRate: 0, consistencyScore: 0, averageValue: 0, targetValue: 0, trend: HealthTrend.STABLE };

  aboutToAppear(): void { this.loadDashboardData(); }

  private loadDashboardData(): void {
    this.isLoading = true;
    setTimeout(() => {
      this.overallHealthScore = 85;
      this.healthTrend = HealthTrend.UP;
      this.fastingStats = { completionRate: 92, consistencyScore: 88, averageValue: 16, targetValue: 16, trend: HealthTrend.UP };
      this.isLoading = false;
    }, 1000);
  }

  private onMetricChange(metric: HealthMetricType): void { this.selectedMetric = metric; }
}

Result : No more state inconsistency bugs. State update response 30% faster. Code simpler — dozens of event-handling lines removed. Development efficiency notably improved.

3. Service Cards: New Way to Boost UX

Problem : Users frequently open app to check fasting progress, water intake. Wanted quicker access.

New Feature : Service cards show core info on home screen without opening app.

Result : Users with service cards show 20% higher daily active rate.

Code: Health Service Card

@Entry(storageLocal)
@Component
export default struct HealthWidget {
  @LocalStorageProp('formTime') @Watch('onFormTimeChange') formTime: string = '';
  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('fastingProgress') fastingProgress: number = 0;
  @LocalStorageProp('todayWaterIntake') todayWaterIntake: number = 0;
  @LocalStorageProp('targetWaterIntake') targetWaterIntake: number = 2000;
  @LocalStorageProp('currentWeight') currentWeight: number = 0;
  @LocalStorageProp('waterCompletion') waterCompletion: number = 0;
  @LocalStorageProp('fastingStatus') fastingStatus: string = 'not_started';
  @LocalStorageProp('lastUpdateTime') lastUpdateTime: string = '';

  onFormTimeChange() {
    postCardAction(this, { action: 'call', abilityName: 'EntryAbility', params: { formId: this.formId, method: 'updateCardInfo', message: 'Call refresh health card.' } });
  }

  build() {
    Column({ space: 8 }) {
      Row() { Text('Health Tracking').fontSize(16).fontWeight(FontWeight.Bold).fontColor($r('app.color.text_primary')).layoutWeight(1) }.width('100%').padding({ left: 12, right: 12, top: 8 })
      Row({ space: 8 }) {
        Column({ space: 4 }) { Text('Fasting Progress').fontSize(12).fontColor($r('app.color.text_secondary')); Row({ space: 4 }) { Text(this.fastingProgress.toString()).fontSize(18).fontWeight(FontWeight.Bold).fontColor(this.getScoreColor(this.fastingProgress)); Text('%').fontSize(12).fontColor($r('app.color.text_secondary')) } }.layoutWeight(1)
        Column({ space: 4 }) { Text('Current Weight').fontSize(12).fontColor($r('app.color.text_secondary')); Row({ space: 4 }) { Text(this.currentWeight.toString()).fontSize(18).fontWeight(FontWeight.Bold).fontColor($r('app.color.text_primary')); Text('kg').fontSize(12).fontColor($r('app.color.text_secondary')) } }.layoutWeight(1)
      }.width('100%').padding({ left: 12, right: 12 })
      Progress({ value: this.fastingProgress, total: 100 }).width('90%').color(this.getScoreColor(this.fastingProgress)).style({ strokeWidth: 4 })
      Row({ space: 8 }) { Column({ space: 2 }) { Text('Today Water').fontSize(10).fontColor($r('app.color.text_secondary')); Text(this.todayWaterIntake.toString() + '/' + this.targetWaterIntake.toString()).fontSize(12).fontColor(this.getScoreColor(this.waterCompletion)).fontWeight(FontWeight.Medium) }.layoutWeight(1) }.width('100%').padding({ left: 12, right: 12, bottom: 8 })
    }.width('100%').height('100%').backgroundColor($r('app.color.background')).borderRadius(12).shadow({ radius: 4, color: $r('app.color.shadow'), offsetX: 0, offsetY: 2 }).onClick(() => { postCardAction(this, { action: 'router', abilityName: 'EntryAbility', params: { message: 'Health card refresh' } }); })
  }

  private getScoreColor(score: number): ResourceColor {
    const colorType = HealthCardUtil.getScoreColor(score);
    switch (colorType) { case 'success': return $r('app.color.success'); case 'warning': return $r('app.color.warning'); case 'error': default: return $r('app.color.error'); }
  }
}

DevEco CodeGenie now offers rapid service card creation.

4. Background Task Management: Ensuring Background Execution

Problem : App needs timely reminders (fasting start/end, water) but background execution was unreliable.

New Feature : Optimized background task manager with long-running and short tasks.

Code: Background Task Service

/** Background task service - ensures notifications in background */
export class BackgroundTaskService {
  private static instance: BackgroundTaskService;
  private notificationService: NotificationService;
  private backgroundTaskId: number = -1;
  private wantAgentObj: WantAgent | null = null;
  private context: common.UIAbilityContext | null = null;

  private constructor() { this.notificationService = NotificationService.getInstance(); this.initWantAgent(); }

  async requestLongRunningTask(): Promise<boolean> {
    try {
      if (!this.wantAgentObj) { await this.initWantAgent(); }
      if (!this.wantAgentObj) { hilog.error(DOMAIN, TAG, 'WantAgent is null'); return false; }
      const bgMode = backgroundTaskManager.BackgroundMode.DATA_TRANSFER;
      await backgroundTaskManager.startBackgroundRunning(this.context!, bgMode, this.wantAgentObj);
      console.log('BackgroundTaskService: Long running task started for hydration reminders');
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, `Failed to start long running task: ${JSON.stringify(error)}`);
      return this.fallbackToLocalNotification();
    }
  }

  async requestShortTask(reason: string): Promise<number> {
    try {
      if (!canIUse('SystemCapability.ResourceSchedule.BackgroundTaskManager.TransientTask')) {
        hilog.warn(DOMAIN, TAG, 'Short task capability not supported');
        await this.fallbackToLocalNotification();
        return -1;
      }
      const delayInfo = backgroundTaskManager.requestSuspendDelay(reason, () => { hilog.info(DOMAIN, TAG, 'Short task expired'); });
      this.backgroundTaskId = delayInfo.requestId;
      hilog.info(DOMAIN, TAG, `Short task requested: ${this.backgroundTaskId}`);
      return this.backgroundTaskId;
    } catch (error) {
      hilog.error(DOMAIN, TAG, `Failed to request short task: ${JSON.stringify(error)}`);
      await this.fallbackToLocalNotification();
      return -1;
    }
  }
  // other methods...
}

Result : Background reminder delivery rate improved from 70% to 95%.

5. Security & Privacy: New Permission Management

Problem : Sensitive health data; poor permission UX (bulk requests, blocking).

New Feature : Granular permission control, request on-demand, transparent flow explaining why each permission is needed.

Code: Secure Data Import/Export

interface BackupInfo { name: string; path: string; date: Date; size: number; }

@Entry
@ComponentV2
struct DataImportExportPage {
  @Local currentTab: number = 0;
  @Local isLoading: boolean = false;
  @Local backups: BackupInfo[] = [];
  @Local showExportDialog: boolean = false;
  @Local showImportDialog: boolean = false;
  @Local showImportResult: boolean = false;
  @Local exportOptions: ExportOptions = { includeSettings: true, format: 'json', compression: false };
  @Local importOptions: ImportOptions = { mergeStrategy: 'merge', validateData: true, backupBeforeImport: true };

  private async exportData(): Promise<void> {
    try {
      this.isExporting = true;
      if (this.selectedDateRange.start && this.selectedDateRange.end) {
        this.exportOptions.dateRange = { start: this.selectedDateRange.start, end: this.selectedDateRange.end };
      } else { this.exportOptions.dateRange = undefined; }
      const filePath = await this.importExportService.exportData(this.exportOptions);
      this.showExportDialog = false;
      if (filePath) { hilog.info(DOMAIN, TAG, `Data exported to: ${filePath}`); ToastUtil.showShort('✅ Data export successful'); }
      else { hilog.error(DOMAIN, TAG, 'Export failed'); ToastUtil.showLong('❌ Data export failed, please retry'); }
    } catch (error) { hilog.error(DOMAIN, TAG, `Export error: ${JSON.stringify(error)}`); this.showExportDialog = false; ToastUtil.showLong('❌ Export error, please retry'); }
    finally { this.isExporting = false; }
  }

  private async importFile(filePath: string): Promise<void> {
    try {
      this.isImporting = true; this.showImportDialog = false;
      const context = this.getUIContext()?.getHostContext() as common.UIAbilityContext;
      if (!context) { hilog.error(DOMAIN, TAG, 'Failed to get context for import'); return; }
      this.importResult = await this.importExportService.importData(filePath, context, this.importOptions);
      this.showImportResult = true;
      if (this.importResult.success) { await this.loadBackups(); }
      hilog.info(DOMAIN, TAG, `Import completed: ${this.importResult.success}`);
    } catch (error) { hilog.error(DOMAIN, TAG, `Import error: ${JSON.stringify(error)}`); }
    finally { this.isImporting = false; }
  }
}

Key practices: data validation before import, automatic pre-import backup, on-demand file permission request, transparent progress/result feedback.

Result : Permission grant rate 65% → 85%. Users praised reasonable permission requests.

App Achievements

1. Development Efficiency

Declarative UI: complex layout dev time 1 day → 0.5 day (50% gain)

Optimized state management: state code -30%, debug time drastically cut

2. Performance Gains

App startup time: 2.5s → 1.5s (40% improvement)

Page transition speed: 1.2s → 0.8s (33% improvement)

Data load time: 1.8s → 1.0s (44% improvement)

Memory usage: 180MB → 120MB (33% reduction)

Users report smoother, more stable experience.

3. User Feedback

92% satisfaction, 15% avg DAU growth, 4.8/5.0 rating.

4. Market Performance

Top 10 in health category within 3 months

100k+ cumulative downloads

Featured as quality app in Huawei AppGallery

Technical Reflections

1. HarmonyOS 6 Transforms Dev Experience

10+ years mobile dev experience. Previously spent huge time on layout, adaptation, state management. HarmonyOS 6 simplified these dramatically. First ArkUI dashboard: 300+ lines → ~100 lines, clearer structure, easier maintenance. Finished early that evening — previously unthinkable.

2. Declarative UI Makes Dev Easier

Favorite feature. Describe UI intuitively, ignore low-level details. Advantages: concise code (50% less), intuitive layout (WYSIWYG), auto-adaptation (one codebase, multi-device), maintainable structure.

3. Cross-Device Development: From Single to Multi-Device Collaboration

Distributed capabilities reshaped design thinking. Must consider phone, tablet, watch simultaneously. App becomes a service across devices, not a single entity. Enables scenarios: plan on phone, reminders on watch, detailed analysis on tablet.

4. Future HarmonyOS Ecosystem Hopes

More third-party libraries/tools

Better docs/tutorials to lower entry barrier

More developers joining ecosystem

More HarmonyOS devices expanding market

Future Plans

1. Feature Roadmap

AI smart recommendations based on diet logs, fasting data, goals

User community for sharing experiences

More health metrics (sleep quality, exercise intensity)

Smart hardware integration (scales, water cups) for auto-sync

2. Tracking HarmonyOS Updates

Will adopt new features promptly. Plan to leverage HarmonyOS 7 AI capabilities for smarter health advice.

3. Ecosystem Collaboration

Partner with professional health institutions for authoritative guidance

Collaborate with smart hardware vendors

Contribute to HarmonyOS ecosystem, share dev experience

Conclusion: Technology Empowering Healthy Living

1. Social Value

Tech serves people, improves life quality. Light Meal Moment is a tool helping users achieve health goals. User feedback: weight loss, better sleep, regular fasting — confirms meaningful impact.

2. Tech & Life Fusion

Combines advanced HarmonyOS 6 with healthy living philosophy. Not just a tech product, but a health companion.

3. Thanks & Outlook

Thanks to Huawei for HarmonyOS platform, AGC reviewers, HarmonyOS ecosystem support. Will keep innovating to deliver better health services. Believes Light Meal Moment will grow stronger within HarmonyOS ecosystem, bringing health and joy to more users. Welcomes fellow HarmonyOS devs and health enthusiasts to connect and grow together.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

mobile developmentperformance-optimizationState ManagementArkUIDeclarative UIBackground TasksHealth App@ComponentV2HarmonyOS 6Service Cards
51CTO HarmonyOS Developer Community
Written by

51CTO HarmonyOS Developer Community

The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.