#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个记步app(一) 原创

RandomBoolean
发布于 2024-8-21 17:37
浏览
0收藏

首先介绍一下要开发的app整体内容。

随行记步

首页顶部调用一言的接口,随机输出几句话,app封装了一个公共接口方法,方便接口的调用。
中间是一个类似代办事项的todo列表。可以通过增删改的方式进行变更。
底部是一个添加目标按钮。

整体如图所示。

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个记步app(一)-鸿蒙开发者社区

点击其中某一项,进入记步详情页。

顶部是一个`
环形进度条,可以单独设置目标步数。
底部是当前步行的数据,起始位置和当前位置。
下方是一个开始、暂停的按钮。效果如图所示。

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个记步app(一)-鸿蒙开发者社区

DB数据库

在开始介绍整体app时,因为项目使用本地数据库存储的方式,需要先完善数据库的封装。

在项目目录创建db文件夹
#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个记步app(一)-鸿蒙开发者社区

创建文件DBUtils.ets,源码如下。此为读写数据库的核心方法。

import { common } from '@kit.AbilityKit';
import fs, { ReadOptions } from '@ohos.file.fs';
import { relationalStore } from '@kit.ArkData';
import { Constant } from '../common/constant/Constant';

/**
 * 将rawfile下的xcz_150.db 写入到手机本地文件中
 * @param context
 */

export async function saveDB2LocalFile(context: common.UIAbilityContext, copyCallback: (isSuccess: boolean) => void) {
  let dirPath = context.getApplicationContext().databaseDir + "/entry"
  if (!fs.accessSync(dirPath)) {
    fs.mkdirSync(dirPath);
  }
  dirPath = dirPath + "/rdb"
  if (!fs.accessSync(dirPath)) {
    fs.mkdirSync(dirPath);
  }

  let dbName: string = Constant.DB_NAME
  if (!fs.accessSync(dirPath + "/" + dbName)) {
    try {
      context.resourceManager.getRawFd('rdb/' + dbName, (error, value) => {
        if (error != null) {
          console.log(`callback getRawFd failed error code: ${error.code}, message: ${error.message}.`);
        } else {
          console.info(value.length.toString() + "DWSD")
          // saveFileToCache(context,value, dbName)
          let cFile = context.getApplicationContext().databaseDir + "/entry/rdb/" + dbName
          let cacheFile = fs.openSync(cFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
          // 读取缓冲区大小
          let bufferSize = 30000
          let buffer = new ArrayBuffer(bufferSize); //创建buffer缓冲区
          // 要copy的文件的offset和length
          let currentOffset = value.offset;
          let lengthNeedToReed = value.length;

          let readOption: ReadOptions = {
            offset: currentOffset, //期望读取文件的位置。可选,默认从当前位置开始读
            length: bufferSize //每次期望读取数据的长度。可选,默认缓冲区长度
          }
          while (true) {
            // 读取buffer容量的内容
            let readLength = fs.readSync(value.fd, buffer, readOption);
            // 写入buffer容量的内容
            fs.writeSync(cacheFile.fd, buffer, { length: readLength }) //写到cacheFile里
            // 判断后续内容 修改读文件的参数
            // buffer没读满代表文件读完了
            if (readLength < bufferSize) {
              break;
            }
            if (readOption.offset) {
              readOption.offset += readLength
            }
            console.log("===============size:" + readOption.offset)
          }
          console.log("Copy Success!!!")
          fs.close(cacheFile);
          copyCallback(true)
        }
      });
    } catch (error) {
      console.error(`callback getRawFd failed, error code: ${error.code}, message: ${error.message}.`)
    }
  }else {
    copyCallback(true)
  }
}

export async  function getRdbStore(context: Context, onGetStore: (store: relationalStore.RdbStore | undefined) => void) {
  relationalStore.getRdbStore(context, {
    name: Constant.DB_NAME,
    securityLevel: relationalStore.SecurityLevel.S1
  }, (err, store) => {
    if (err) {
      console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
      return;
    } else {
      console.info(`Succeeded in getting RdbStore.`);
      onGetStore(store)
      console.log("=======version:" + store.version)
    }
    // this.rdbStore = store;

  })
}

Constant如下

export class Constant {
  static readonly DB_NAME = "task.db"
}

实现一个增删改的方法。附带分页的实现。

import { BusinessError } from '@kit.BasicServicesKit'
import { Task } from '../model/Task'
import { relationalStore, ValuesBucket } from '@kit.ArkData'

class TaskModel {
  /**任务列表,带分页
   */
  getTasks(store: relationalStore.RdbStore, onGetTasks: (res: ESObject) => void, onError?: (err: string) => void, limit?: number, skip?: number) {
    let predicates = new relationalStore.RdbPredicates("task")
    predicates.limitAs(limit)
    predicates.offsetAs(skip)
    predicates.orderByDesc("id")
    store.query(predicates, (err: BusinessError, resultSet) => {
      if (err == undefined) {
        let tasks = Array<Task>()
        while (resultSet.goToNextRow()) {
          tasks.push(this.parseTask(resultSet))
        }
        onGetTasks(tasks)
        console.log("=====" + JSON.stringify(tasks))
      } else {
        onError!(err.message)
      }
    })
  }

  addTask(store: relationalStore.RdbStore, valueBucket: ValuesBucket, onAddSuccess?: (res: ESObject) => void, onError?: (err: ESObject) => void) {
    store.insert("task", valueBucket, (err: BusinessError, rows: number) => {
      if (err) {
        if (onError) {
          onError("")
        }
        return
      }
      onAddSuccess!("")

    })
  }

  //https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-data-rdb-0000001860176021
  updateTask(store: relationalStore.RdbStore, valueBucket: ValuesBucket, id: number) {
    let predicates = new relationalStore.RdbPredicates("task")
    predicates.equalTo('id', id)
    store.update(valueBucket, predicates, (err: BusinessError, rows: number) => {
      if (err) {
        console.info("Updated failed, err: " + err)
        return
      }
      console.log("Updated row count: " + rows)
    })
  }

  deleteTask(store: relationalStore.RdbStore) {
    let predicates = new relationalStore.RdbPredicates("task")
    predicates.equalTo("id", 123)
    store.delete(predicates)
  }

  parseTask(resultSet: relationalStore.ResultSet): Task {
    let task = new Task()
    task.id = this.parseLong("id", resultSet)
    task.progressValue = this.parseLong("progressValue", resultSet)
    task.type = this.parseLong("type", resultSet)
    task.taskName = this.parseString("taskName", resultSet)
    task.remark = this.parseString("remark", resultSet)
    task.createDate = this.parseString("createDate", resultSet)
    task.updateDate = this.parseString("updateDate", resultSet)
    task.cu_id = this.parseString("cu_id", resultSet)
    task.pic = this.parseString("pic", resultSet)
    return task
  }

  parseLong(column: string, resultSet: relationalStore.ResultSet): number {
    if (resultSet.getColumnIndex(column) != -1) {
      return resultSet.getLong(resultSet.getColumnIndex(column));
    }
    return 0
  }

  parseString(column: string, resultSet: relationalStore.ResultSet): string {
    if (resultSet.getColumnIndex(column) != -1) {
      return resultSet.getString(resultSet.getColumnIndex(column));
    }
    return ""
  }
}

export let taskModel = new TaskModel()

使用方式,通过引入taskModel调用其实现的方法。在aboutToAppear初始化数据库,赋值store。

import { taskModel } from '../db/TaskModel';
import { getRdbStore, saveDB2LocalFile } from '../db/DBUtils';


@Provide store: relationalStore.RdbStore | undefined = undefined
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext


  aboutToAppear(): void {

    saveDB2LocalFile(this.context, (isSuccess: boolean) => {
      if (isSuccess) {
        getRdbStore(this.context, (store: relationalStore.RdbStore | undefined) => {
          this.store = store
        })
      }
    })

  }

const valueBucket: ValuesBucket = {
      "taskName": taskName,
      "remark": runNum,
      "progressValue": 0,
      "updateDate": getCurrentTime()
    };

taskModel.addTask(this.store, valueBucket, () => {
        emitter.emit("addTaskSuccess")
        this.dialogController.close()
      })

这里还用到了emitter事件的触发。this.store为必传参数。

app首页的完整源码如下

/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License,Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { promptAction } from '@kit.ArkUI';
import TargetInformation from '../view/TargetInformation';
import AddTargetDialog from '../view/AddTargetDialog';
import TargetList from '../view/TargetList';
import TaskItemModel from '../viewmodel/TaskItemModel';
import DataModel from '../viewmodel/DataModel';
import { CommonConstants } from '../common/constant/CommonConstant';
import getCurrentTime from '../common/utils/DateUtil';
import { relationalStore, ValuesBucket } from '@kit.ArkData';
import { taskModel } from '../db/TaskModel';
import { emitter } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { getRdbStore, saveDB2LocalFile } from '../db/DBUtils';

@Entry
@Component
export struct MainPage {
  @State targetData: Array<TaskItemModel> = DataModel.getData();
  @State totalTasksNumber: number = 0;
  @State completedTasksNumber: number = 0;
  @State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE;
  @Provide @Watch('onProgressChanged') overAllProgressChanged: boolean = false;
  @State limit: number = 20
  @State skip: number = 0
  dialogController: CustomDialogController = new CustomDialogController({
    builder: AddTargetDialog({
      onClickOk: (value: string, runNum): void => this.saveTask(value, runNum)
    }),
    alignment: DialogAlignment.Bottom,
    offset: {
      dx: CommonConstants.DIALOG_OFFSET_X,
      dy: $r('app.float.dialog_offset_y')
    },
    customStyle: true,
    autoCancel: false
  });


  @Provide store: relationalStore.RdbStore | undefined = undefined
  context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext

  /**
   * Listening targetData.
   */
  onProgressChanged() {
    this.totalTasksNumber = this.targetData.length;
    this.completedTasksNumber = this.targetData.filter((item: TaskItemModel) => {
      return item.progressValue === CommonConstants.SLIDER_MAX_VALUE;
    }).length;
    this.latestUpdateDate = getCurrentTime();
  }

  aboutToAppear(): void {

    saveDB2LocalFile(this.context, (isSuccess: boolean) => {
      if (isSuccess) {
        getRdbStore(this.context, (store: relationalStore.RdbStore | undefined) => {
          this.store = store
        })
      }
    })

  }

  build() {
    Column() {
      this.titleBar()
      TargetInformation({
        latestUpdateDate: this.latestUpdateDate,
        totalTasksNumber: this.totalTasksNumber,
        completedTasksNumber: this.completedTasksNumber
      })
      TargetList({
        targetData: $targetData,
        onAddClick: (): void => this.dialogController.open()
      })// .height(CommonConstants.LIST_BOARD_HEIGHT)
        .layoutWeight(1)
    }
    .width(CommonConstants.FULL_WIDTH)
    .height(CommonConstants.FULL_HEIGHT)
    .backgroundColor($r('app.color.index_background'))
  }

  @Builder
  titleBar() {
    Text('随行记步')
      .width(CommonConstants.TITLE_WIDTH)
      .height($r('app.float.title_height'))
      .fontSize($r('app.float.title_font'))
      .fontWeight(CommonConstants.FONT_WEIGHT_LARGE)
      .textAlign(TextAlign.Start)
      .margin({
        top: $r('app.float.title_margin'),
        bottom: $r('app.float.title_margin')
      })
  }

  /**
   * Save the progress value and update time after you click OK in the dialog box.
   *
   * @param taskName Latest Progress Value.
   */
  saveTask(taskName: string, runNum: number) {
    if (taskName === '') {
      promptAction.showToast({
        message: $r('app.string.cannot_input_empty'),
        duration: CommonConstants.TOAST_TIME,
        bottom: CommonConstants.TOAST_MARGIN_BOTTOM
      });
      return;
    }

    if (!runNum) {
      promptAction.showToast({
        message: '请设置步数',
        duration: CommonConstants.TOAST_TIME,
        bottom: CommonConstants.TOAST_MARGIN_BOTTOM
      });
      return;
    }
    const valueBucket: ValuesBucket = {
      "taskName": taskName,
      "remark": runNum,
      "progressValue": 0,
      "updateDate": getCurrentTime()
    };

    if (this.store) {
      taskModel.addTask(this.store, valueBucket, () => {
        emitter.emit("addTaskSuccess")
        this.dialogController.close()
      })
    }

    // DataModel.addData(new TaskItemModel(taskName, 0, getCurrentTime()));
    // this.targetData = DataModel.getData();
    // this.overAllProgressChanged = !this.overAllProgressChanged;
    // this.dialogController.close();
  }
}

重要

使用数据库之前需要先创建好表结构,然后将db文件放到如下图所示的位置中。

#HarmonyOS NEXT体验官# 体验HarmonyOS开发流程,开发一个记步app(一)-鸿蒙开发者社区

核心除了接口的调用,就是对于数据库的使用。篇幅所限,本文介绍了数据库相关的使用,因为本app依赖于此所以介绍的有些多。后续就是其他的一些实现。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐