DEV Community

linzhongxue
linzhongxue

Posted on

Practical Development of Smart Health Monitoring Applications Based on HarmonyOS Next

Practical Development of Smart Health Monitoring Applications Based on HarmonyOS Next

1. Project Overview and Development Environment Setup

1.1 Project Background and Functional Planning

With the popularity of smart wearable devices, health management applications have become an important part of the HarmonyOS ecosystem. This project will develop a smart health monitoring application based on HarmonyOS Next, with the following main features:

  • Real-time heart rate monitoring and historical data display
  • Daily step counting and exercise goal setting
  • Sleep quality analysis and recommendations
  • Visualization of health data
  • Alerts for abnormal health indicators

1.2 Configuring the DevEco Studio Development Environment

First, ensure that the latest version of DevEco Studio is installed and basic configurations are completed:

  1. Download DevEco Studio 4.0 or later from the official website.
  2. Install Node.js and the Ohpm package manager.
  3. Configure the HarmonyOS SDK, selecting API version 9 or higher.
  4. When creating a new project, select "Application" → "Empty Ability."
// Example of the basic project configuration file: entry/build-profile.json5  
{  
  "app": {  
    "signingConfigs": [],  
    "compileSdkVersion": 9,  
    "compatibleSdkVersion": 9,  
    "products": [  
      {  
        "name": "default",  
        "signingConfig": "default"  
      }  
    ]  
  },  
  "modules": [  
    {  
      "name": "entry",  
      "srcPath": "./src/main/ets",  
      "targets": [  
        {  
          "name": "default",  
          "applyToProducts": [  
            "default"  
          ]  
        }  
      ]  
    }  
  ]  
}  
Enter fullscreen mode Exit fullscreen mode

2. Building the Application Framework

2.1 Application Entry and Permission Configuration

Configure basic application information and required permissions in entry/src/main/module.json5:

{  
  "module": {  
    "name": "healthmonitor",  
    "type": "entry",  
    "description": "$string:module_desc",  
    "mainElement": "MainAbility",  
    "deviceTypes": [  
      "smartwatch",  
      "phone",  
      "tablet"  
    ],  
    "abilities": [  
      {  
        "name": "MainAbility",  
        "srcEntry": "./ets/MainAbility/MainAbility.ts",  
        "label": "$string:MainAbility_label",  
        "icon": "$media:icon",  
        "startWindowIcon": "$media:icon",  
        "startWindowBackground": "$color:start_window_background",  
        "exported": true,  
        "backgroundModes": [  
          "healthData"  
        ]  
      }  
    ],  
    "requestPermissions": [  
      {  
        "name": "ohos.permission.READ_HEALTH_DATA"  
      },  
      {  
        "name": "ohos.permission.WRITE_HEALTH_DATA"  
      },  
      {  
        "name": "ohos.permission.ACTIVITY_MOTION"  
      }  
    ]  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

2.2 Designing the Main Page Layout

Use ArkTS declarative UI to build the main interface of the health application:

// src/main/ets/pages/Index.ets  
@Entry  
@Component  
struct HealthMainPage {  
  @State currentTab: string = 'dashboard'  

  build() {  
    Column() {  
      // Top health data overview  
      HealthOverview()  

      // Main content area  
      TabContent(this.currentTab)  

      // Bottom navigation bar  
      Tabs({ barPosition: BarPosition.End }) {  
        TabContent() {  
          DashboardPage()  
        }.tabBar('Home', 'home.svg', 'home_filled.svg')  

        TabContent() {  
          ActivityPage()  
        }.tabBar('Activity', 'activity.svg', 'activity_filled.svg')  

        TabContent() {  
          SleepPage()  
        }.tabBar('Sleep', 'sleep.svg', 'sleep_filled.svg')  

        TabContent() {  
          ProfilePage()  
        }.tabBar('Profile', 'profile.svg', 'profile_filled.svg')  
      }  
      .barMode(BarMode.Fixed)  
      .onChange((index: number) => {  
        this.currentTab = ['dashboard', 'activity', 'sleep', 'profile'][index]  
      })  
    }  
    .width('100%')  
    .height('100%')  
    .backgroundColor('#F5F5F5')  
  }  
}  

@Component  
struct HealthOverview {  
  @State heartRate: number = 72  
  @State steps: number = 4582  
  @State sleepHours: number = 6.5  

  build() {  
    Row() {  
      // Heart rate card  
      HealthMetricCard('Heart Rate', `${this.heartRate} bpm`, 'heart.svg', '#FF6B81')  

      // Steps card  
      HealthMetricCard('Steps', this.steps.toString(), 'footsteps.svg', '#4CAF50')  

      // Sleep card  
      HealthMetricCard('Sleep', `${this.sleepHours} h`, 'moon.svg', '#5D6DBE')  
    }  
    .padding(16)  
    .justifyContent(FlexAlign.SpaceBetween)  
  }  
}  

@Component  
struct HealthMetricCard {  
  private title: string  
  private value: string  
  private icon: string  
  private color: string  

  build() {  
    Column() {  
      Image(this.icon)  
        .width(24)  
        .height(24)  
        .margin({ bottom: 8 })  

      Text(this.title)  
        .fontSize(12)  
        .fontColor('#666')  

      Text(this.value)  
        .fontSize(18)  
        .fontWeight(FontWeight.Bold)  
        .margin({ top: 4 })  
    }  
    .padding(16)  
    .backgroundColor(Color.White)  
    .borderRadius(12)  
    .shadow({ radius: 8, color: '#10000000', offsetX: 0, offsetY: 2 })  
    .width('30%')  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

3. Health Data Collection and Processing

3.1 Integrating HarmonyOS Health Data Services

HarmonyOS provides rich health data APIs. First, we need to initialize the health data manager:

// src/main/ets/utils/HealthDataManager.ts  
import { health } from '@kit.HealthKit';  

export class HealthDataManager {  
  private static instance: HealthDataManager;  
  private healthDataHelper: health.HealthDataHelper;  

  private constructor() {  
    this.healthDataHelper = health.createHealthDataHelper();  
  }  

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

  // Get today's step count  
  async getTodaySteps(): Promise<number> {  
    try {  
      const endTime = new Date();  
      const startTime = new Date();  
      startTime.setHours(0, 0, 0, 0);  

      const options: health.HealthDataOptions = {  
        startTime: startTime.getTime(),  
        endTime: endTime.getTime(),  
        dataType: health.DataType.DATA_TYPE_STEP_COUNT  
      };  

      const result = await this.healthDataHelper.query(options);  
      return result?.length > 0 ? result[0].value : 0;  
    } catch (err) {  
      console.error(`Failed to get steps: ${err.code}, ${err.message}`);  
      return 0;  
    }  
  }  

  // Get real-time heart rate  
  async getLatestHeartRate(): Promise<number> {  
    try {  
      const options: health.HealthDataOptions = {  
        dataType: health.DataType.DATA_TYPE_HEART_RATE,  
        sort: { field: 'startTime', order: 'desc' },  
        limit: 1  
      };  

      const result = await this.healthDataHelper.query(options);  
      return result?.length > 0 ? result[0].value : 0;  
    } catch (err) {  
      console.error(`Failed to get heart rate: ${err.code}, ${err.message}`);  
      return 0;  
    }  
  }  

  // Subscribe to heart rate changes  
  subscribeHeartRate(callback: (value: number) => void): void {  
    const options: health.HealthDataOptions = {  
      dataType: health.DataType.DATA_TYPE_HEART_RATE  
    };  

    this.healthDataHelper.on('healthDataChange', options, (data) => {  
      if (data?.length > 0) {  
        callback(data[0].value);  
      }  
    });  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

3.2 Using Health Data in Pages

Integrate health data into pages for real-time updates:

// src/main/ets/pages/DashboardPage.ets  
@Component  
struct DashboardPage {  
  @State heartRate: number = 0  
  @State steps: number = 0  
  @State sleepHours: number = 0  
  private healthManager = HealthDataManager.getInstance()  

  aboutToAppear() {  
    this.loadHealthData()  
    this.setupSubscriptions()  
  }  

  private async loadHealthData() {  
    this.heartRate = await this.healthManager.getLatestHeartRate()  
    this.steps = await this.healthManager.getTodaySteps()  
    // Sleep data requires more complex processing; simplified here  
    this.sleepHours = 7.2  
  }  

  private setupSubscriptions() {  
    this.healthManager.subscribeHeartRate((rate) => {  
      this.heartRate = rate  
    })  

    // Refresh step data every 5 minutes  
    setInterval(async () => {  
      this.steps = await this.healthManager.getTodaySteps()  
    }, 300000)  
  }  

  build() {  
    Column() {  
      // Heart rate chart  
      HealthLineChart('Heart Rate Trend', this.heartRate, 60, 100, 'bpm')  

      // Steps progress ring  
      ProgressRing('Today’s Steps', this.steps, 10000, '#4CAF50')  

      // Sleep quality card  
      SleepQualityCard(this.sleepHours)  
    }  
    .padding(16)  
  }  
}  

@Component  
struct HealthLineChart {  
  private title: string  
  @State private currentValue: number  
  private min: number  
  private max: number  
  private unit: string  
  @State private historyData: number[] = []  

  build() {  
    Column() {  
      Text(`${this.title}: ${this.currentValue} ${this.unit}`)  
        .fontSize(16)  
        .fontWeight(FontWeight.Bold)  

      Line()  
        .width('90%')  
        .height(2)  
        .backgroundColor('#EEE')  
        .margin({ top: 8, bottom: 16 })  

      // Simplified chart implementation  
      Row() {  
        ForEach(this.historyData, (value, index) => {  
          Column() {  
            Line()  
              .height(value / this.max * 100)  
              .width(4)  
              .backgroundColor('#FF6B81')  
              .margin({ right: 4 })  
          }  
          .height(100)  
          .alignItems(VerticalAlign.Bottom)  
        })  
      }  
      .height(100)  
      .margin({ bottom: 16 })  
    }  
    .padding(16)  
    .backgroundColor(Color.White)  
    .borderRadius(12)  
    .width('100%')  
    .onAppear(() => {  
      // Simulate historical data  
      this.historyData = Array.from({ length: 24 }, (_, i) =>  
        Math.min(this.max, Math.max(this.min, this.currentValue + (Math.random() * 20 - 10)))  
      )  
    })  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

4. Advanced Feature Implementation

4.1 Abnormal Heart Rate Alert System

// src/main/ets/utils/HealthAlarmSystem.ts  
import { HealthDataManager } from './HealthDataManager';  
import { common, notification } from '@kit.AbilityKit';  

export class HealthAlarmSystem {  
  private static instance: HealthAlarmSystem;  
  private healthManager = HealthDataManager.getInstance();  
  private normalRange: [number, number] = [60, 100];  
  private context: common.Context | null = null;  

  private constructor() {}  

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

  public init(context: common.Context): void {  
    this.context = context;  
    this.setupHeartRateMonitoring();  
  }  

  private setupHeartRateMonitoring(): void {  
    this.healthManager.subscribeHeartRate((rate) => {  
      this.checkHeartRate(rate);  
    });  
  }  

  private checkHeartRate(rate: number): void {  
    if (rate < this.normalRange[0]) {  
      this.showNotification('Low Heart Rate', `Your heart rate is low (${rate} bpm). Consider resting.`);  
    } else if (rate > this.normalRange[1]) {  
      this.showNotification('High Heart Rate', `Your heart rate is high (${rate} bpm). Stay calm.`);  
    }  
  }  

  private showNotification(title: string, content: string): void {  
    if (!this.context) return;  

    const wantAgent: notification.WantAgent = {  
      wants: [  
        {  
          bundleName: this.context.applicationInfo.name,  
          abilityName: 'MainAbility'  
        }  
      ],  
      operationType: notification.OperationType.START_ABILITY,  
      requestCode: 0  
    };  

    notification.publish({  
      content: {  
        contentType: notification.ContentType.NOTIFICATION_TEXT,  
        normal: {  
          title: title,  
          text: content,  
          additionalText: 'Health Alert'  
        }  
      },  
      id: 1,  
      wantAgent: wantAgent  
    }).catch(err => {  
      console.error(`Failed to show notification: ${err.code}, ${err.message}`);  
    });  
  }  

  public setNormalRange(min: number, max: number): void {  
    this.normalRange = [min, max];  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

4.2 Sleep Quality Analysis Algorithm

// src/main/ets/utils/SleepAnalyzer.ts  
import { health } from '@kit.HealthKit';  

export class SleepAnalyzer {  
  private healthDataHelper = health.createHealthDataHelper();  

  async analyzeSleepQuality(date: Date): Promise<SleepAnalysisResult> {  
    const startTime = new Date(date);  
    startTime.setHours(0, 0, 0, 0);  

    const endTime = new Date(date);  
    endTime.setHours(23, 59, 59, 999);  

    try {  
      const options: health.HealthDataOptions = {  
        startTime: startTime.getTime(),  
        endTime: endTime.getTime(),  
        dataType: health.DataType.DATA_TYPE_SLEEP_STATE  
      };  

      const sleepData = await this.healthDataHelper.query(options);  
      return this.processSleepData(sleepData);  
    } catch (err) {  
      console.error(`Failed to analyze sleep: ${err.code}, ${err.message}`);  
      return {  
        totalHours: 0,  
        deepSleepHours: 0,  
        lightSleepHours: 0,  
        remHours: 0,  
        wakeTimes: 0,  
        score: 0  
      };  
    }  
  }  

  private processSleepData(data: health.HealthData[]): SleepAnalysisResult {  
    let totalSleep = 0;  
    let deepSleep = 0;  
    let lightSleep = 0;  
    let remSleep = 0;  
    let wakeCount = 0;  
    let lastState: string | null = null;  

    data.forEach(record => {  
      const duration = (record.endTime - record.startTime) / (1000 * 60 * 60); // Convert to hours  

      switch (record.value) {  
        case health.SleepState.SLEEP_STATE_DEEP:  
          deepSleep += duration;  
          totalSleep += duration;  
          break;  
        case health.SleepState.SLEEP_STATE_LIGHT:  
          lightSleep += duration;  
          totalSleep += duration;  
          break;  
        case health.SleepState.SLEEP_STATE_REM:  
          remSleep += duration;  
          totalSleep += duration;  
          break;  
        case health.SleepState.SLEEP_STATE_AWAKE:  
          if (lastState !== health.SleepState.SLEEP_STATE_AWAKE) {  
            wakeCount++;  
          }  
          break;  
      }  

      lastState = record.value;  
    });  

    // Simplified sleep scoring algorithm  
    const score = Math.min(100,  
      Math.round(  
        (deepSleep * 40 + lightSleep * 20 + remSleep * 30) / totalSleep * 10 -  
        wakeCount * 2 +  
        (totalSleep >= 7 ? 20 : totalSleep >= 5 ? 10 : 0)  
      )  
    );  

    return {  
      totalHours: parseFloat(totalSleep.toFixed(1)),  
      deepSleepHours: parseFloat(deepSleep.toFixed(1)),  
      lightSleepHours: parseFloat(lightSleep.toFixed(1)),  
      remHours: parseFloat(remSleep.toFixed(1)),  
      wakeTimes: wakeCount,  
      score: score  
    };  
  }  
}  

interface SleepAnalysisResult {  
  totalHours: number;  
  deepSleepHours: number;  
  lightSleepHours: number;  
  remHours: number;  
  wakeTimes: number;  
  score: number;  
}  
Enter fullscreen mode Exit fullscreen mode

5. Application Optimization and Release Preparation

5.1 Performance Optimization Recommendations

  1. Data Caching Strategy: Implement local caching for frequently accessed health data.
// Example caching implementation  
import { dataStorage } from '@kit.ArkData';  

class HealthDataCache {  
  private static PREFIX = 'health_cache_';  

  static async set(key: string, value: any, ttl: number = 3600): Promise<void> {  
    try {  
      const cacheItem = {  
        data: value,  
        expires: Date.now() + ttl * 1000  
      };  
      await dataStorage.put(this.PREFIX + key, JSON.stringify(cacheItem));  
    } catch (err) {  
      console.error(`Cache set error: ${err.code}, ${err.message}`);  
    }  
  }  

  static async get(key: string): Promise<any | null> {  
    try {  
      const value = await dataStorage.get(this.PREFIX + key);  
      if (!value) return null;  

      const cacheItem = JSON.parse(value);  
      if (cacheItem.expires < Date.now()) {  
        await this.delete(key);  
        return null;  
      }  
      return cacheItem.data;  
    } catch (err) {  
      console.error(`Cache get error: ${err.code}, ${err.message}`);  
      return null;  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode
  1. UI Rendering Optimization: Use LazyForEach for long lists.
@Component  
struct SleepHistoryList {  
  @State sleepData: SleepAnalysisResult[] = []  

  build() {  
    List() {  
      LazyForEach(this.sleepData, (item: SleepAnalysisResult) => {  
        ListItem() {  
          SleepHistoryItem({ data: item })  
        }  
      }, (item) => item.date.toString())  
    }  
    .height('100%')  
    .width('100%')  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

5.2 Application Testing and Debugging

  1. Use DevEco Studio's previewer for quick UI validation.
  2. Test health data APIs on real devices.
  3. Use HiLog for debug logging.
import { hilog } from '@kit.PerformanceAnalysisKit';  

const DOMAIN = 0xFF00;  
const TAG = 'HealthApp';  

function logDebug(msg: string): void {  
  hilog.debug(DOMAIN, TAG, msg);  
}  

function logError(msg: string): void {  
  hilog.error(DOMAIN, TAG, msg);  
}  
Enter fullscreen mode Exit fullscreen mode

5.3 Application Packaging and Release

  1. Configure application signing.
  2. Generate HAP package.
  3. Submit to AppGallery Connect for review.
  4. Configure application privacy policy.
// Example privacy dialog  
@Component  
struct PrivacyDialog {  
  @State showDialog: boolean = true  

  build() {  
    if (this.showDialog) {  
      AlertDialog({  
        title: 'Privacy Policy',  
        message: 'We take your privacy very seriously...',  
        primaryButton: {  
          value: 'Agree',  
          action: () => {  
            this.showDialog = false;  
            // Store user consent  
            Preferences.put('privacy_accepted', true);  
          }  
        },  
        secondaryButton: {  
          value: 'Disagree',  
          action: () => {  
            // Exit the application  
            AppStorage.setOrCreate('exitApp', true);  
          }  
        }  
      })  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

6. Summary and Expansion Suggestions

This tutorial provides a comprehensive guide to developing a health management application based on HarmonyOS Next, covering all aspects from basic framework setup to advanced feature implementation. Using ArkTS declarative UI development and HarmonyOS health data services, we have built a fully functional health monitoring application.

Expansion Suggestions:

  1. Integrate more health metrics such as blood oxygen and blood pressure monitoring.
  2. Add health recommendations and exercise planning features.
  3. Implement multi-device data synchronization.
  4. Develop a watch version of the application with optimized UI adaptation.
  5. Incorporate AI models for health prediction analysis.

Through continuous iteration and improvement, you can create a more professional and intelligent health management application, providing users with comprehensive health monitoring services.

Top comments (0)