挑战ArkUI复刻手机备忘录(Rdb数据库)

网友投稿 805 2023-06-06

挑战ArkUI复刻手机备忘录(Rdb数据库)

挑战ArkUI复刻手机备忘录(Rdb数据库)

​​想了解更多关于开源的内容,请访问:​​

​​ 开源基础软件社区​​

前言

学习了关系型数据库和一些相关的codelabs后,为了更深入地了解ArkUI关系型数据库的使用和操作,我决定复刻一个小小的手机备忘录。整个实现过程不仅有对关系型数据库接口的尝试封装,还碰了各种UI实现、路由跳转的壁,印象很深,所以就想分享一下这次复刻实现的过程,总结一下经验。由于写的代码量较大,下面主要说一下与数据库通信的环节和路由通信的环节,UI布局放在上传的zip里。

部分效果展示

这是主界面(是不是有点像:-):

由于界面功能较多,更多的gif在后面功能拆分环节再来展示

代码实现流程

代码结构:

实现思路

首先把繁多的rdb数据库接口封装成几个功能较完备的大接口,在UI界面进行调用和数据处理、数据通信。

一、关系型数据库的封装

官方对它的宣传就是:不用学会sql也能用(确实对,但还是要有些数据库基础才能处理结果集resultSet和使用executeSql接口)。

导入包:

import data_rdb from ‘@ohos.data.rdb’。

创建数据库:

data_rdb.getRdbStore返回一个数据库管理对象。

参数名

类型

说明

context

Context

应用的上下文。

config

StoreConfig

与此RDB存储相关的数据库配置。

version

number

数据库版本。

众所周知,数据库有增删改查四大操作,则它也提供了几个常用接口。

api

data_rdb.insert

data_rdb.query

data_rdb.delete

data_rdb.update

executeSql

其中,插入操作只需一个用于存储键值对的valueBucket就够了,而删除、更新和查询都要借助RdbPredicates谓词来获取数据索引才能进一步操作:

RdbPredicates

表示关系型数据库(RDB)的谓词。该类确定RDB中条件表达式的值是true还是false。

let predicates = new data_rdb.RdbPredicates(“EMPLOYEE”)//例如创建一个为"EMPLOYEE"表服务的predicates实例。

还有更多的跟分布式有关的接口:

set***s

obtain***Name

sync

还需要简单了解一下结果集的使用。

​​结果集文档​​

下面是看一些codelabs后进一步封装成的rdbStoreServer接口。

操作

接口名

插入

insertValue

更新

updateValue

列表查询

queryValue

限定词查询

search

删除

deleteValue

封装了五个接口,其中把查询参数以及查询得到的结果集从封装中解耦出来,能更灵活的查询数据和处理结果集。比如查询:通过field、value、table(表名)三个参数查询后获得结果集后再回调处理,如下:

rdbStoreServer.js:

import featureAbility from '@ohos.ability.featureAbility'import data_rdb from '@ohos.data.rdb'const STORE_CONFIG = { name: "rdbStore.db" }export default class rbdStoreModel { rdbStore #tableList = [] constructor(SQL_CREATE_TABLE_LIST) { //---在构造时传入准备好的sql语句 for (let i in SQL_CREATE_TABLE_LIST) {//---可插入多条SQL语句,一个库建多个表 this.#tableList.push(SQL_CREATE_TABLE_LIST[i]) } } createKvStore(cb) { if (typeof (this.rdbStore) === 'undefined') { let self = this; let context = featureAbility.getContext(); //---获取上下文 let promise = data_rdb.getRdbStore(context, STORE_CONFIG, 1) promise.then((rdbStore) => { self.rdbStore = rdbStore; for (let i in this.#tableList) { rdbStore.executeSql(this.#tableList[i], null); } console.info("xxx--- rdbStore " + 'create table.') cb(); }).catch((err) => { console.info("xxx--- rdbStore " + err) cb(); }) } else { cb(); } } insertValue(table, valueBucket) { this.createKvStore(() => { let promise = this.rdbStore.insert(table, valueBucket) promise.then((rows) => { console.info('xxx--- rdbStore.insert done ' + rows) }) }) } updateValue(valueBucket, table, field, value) { this.createKvStore(() => { console.info(`xxx--- rdbStore.update field = ${field} value = ${value} == ${JSON.stringify(valueBucket)}`) let predicates = new data_rdb.RdbPredicates(table) predicates.equalTo(field, value) this.rdbStore.update(valueBucket, predicates, function (err, rows) { console.info("xxx--- rdbStore.update updated row count: " + rows) }) }) } deleteValue(table, field, value,cb) { this.createKvStore(() => { console.info(`xxx--- rdbStore.delete field = ${field} value = ${value}`) let predicates = new data_rdb.RdbPredicates(table) predicates.equalTo(field, value) this.rdbStore.delete(predicates, (err, rows) => { if (rows === 0) { console.info("xxx--- rdbStore.delete rows: -1") } else console.info("xxx--- rdbStore.delete rows: " + rows) cb() }) }) } queryValue(table,callback) { //---所有columns值查找 this.createKvStore(() => { let predicates = new data_rdb.RdbPredicates(table) let promise = this.rdbStore.query(predicates)//---当query没有columns键值对集时默认返回所有columns console.info("xxx--- rdbStore query start") promise.then((resultSet) => { callback(resultSet); }) }) } search(table,field,value,callback){ //---限定词查找 this.createKvStore(()=>{ let predicates = new data_rdb.RdbPredicates(table) predicates.contains(field,value) let promise = this.rdbStore.query(predicates) console.info("xxx--- rdbStore search start") promise.then((resultSet) => { callback(resultSet); }) }) }}

index.js:

二、主界面调用增删改查

1、页面初始化

两级UI:

第一级页面:

1、主界面index的UI是熟悉的组件list列表渲染和block的条件渲染,展示数据库里的每条数据和提供增删改查交互,这里不多说;

2、在onShow生命周期事件加入查询操作,每一次页面展示(添加和更新操作完成后跳转回来时)都会查询一次,以保证数据是最新的。

3、渲染时,单条数据分为标题和内容、标题最大长度为5、内容溢出的用冒号…来省略。

4、第一级页面集成了删除和查询操作。

{{ $item.title }} {{ $item.content }}

第二级页面:

1、对单条数据的具体操作页面,分为两个操作:添加和改变数据,需要区分。

​​textarea组件链接​​。

//--softkeyboardenabled属性为编辑时是否弹出系统软键盘

2、增code:1

增加和更新与主页面之间的页面通信依靠路由通信。

index.js:(onShow和路由跳转放在后面的更新操作一起说)。

add() { router.push({ url: 'pages/writeNotes/writeNotes', params: { info: { title: '', //---新建,传递数据为空 content: '', code: 1 //---1 代表为增加操作,对应数据库insert接口 } } }) },

(顺带提一句:里面的黑色数字软键盘是我上篇那个自定义组件,响应比百度输入法快一点我就用了)

3、改code:0

更新实现思路:

路由跳转

这里要用到路由页面的跳转,在这碰了壁:router.back()和router.push()在传参中用法都一样,但是实际调用的时候我采用router.back({url,params})的方式时在index.js页面却接收不到传递回来的参数,但router.push可以,而两者又有很明显的区别:back()是返回上一级页面,那个页面的数据仍然保留在后台缓存;而push则会新建一个页面,上级页面的数据保留不了,也要手动用router.clear()清除之前旧的页面,但也只能先采用router.push的方式。

index.js:

click(title, content, id) { if (this.deleteListener === false) { router.push({ url: 'pages/writeNotes/writeNotes', params: { info: { id: id, //---传递列表中已有数据:标题和内容,方便更新 title: title, content: content, code: 0 //---1 代表为更新操作,对应数据库update接口 } } }) } }, onShow() { let params = router.getParams() //---getParams方法接收路由传参 if (params) { let info = params.info let code = info.code let id = info.id let title = info.title let content = info.content console.info(`xxx--- info receive ${code} ${title} ${content}`) let obj = { title: title, content: content } if (code == 0) { this.update(obj, id, () => { setTimeout(() => { //---采用延时查询,避免数据不同步 this.query() }, 400) }) } else { this.insert(obj, () => { //---采用延时查询,避免数据不同步 setTimeout(() => { this.query() }, 400) }) } } else this.query() },

writeNotes.js:

update() { console.info(`xxx--- info submit ${this.code} ${this.title} ${this.inputText} `) router.clear() //---清除之前多余的页面 let info if (this.code == 0) { //---更新操作时 info = { id: this.id, title: this.title, content: this.inputText, code: this.code } } else { //---增加操作时 info = { title: this.title, content: this.inputText, code: this.code } } router.push({ url: 'pages/index/index', params: { info: info } }) },

4、删

删除操作(支持多选):

删除的操作比较简单,就是支持多选的功能复杂一点。

删除实现思路:

block控制复选框和其它按钮的渲染,长按后显示或隐藏。选中数据块的id值存入待删除列表deleteList,再通过delete接口逐个删除,最后查询列表、清空deleteList。

checkboxOnChange(id, e) { //---选择删除 if (e.checked) { this.deleteList.push(id) } else { let index = this.deleteList.find(e => e === id) this.deleteList.splice(index, 1) } }, deletePress(idx, id) { this.isCheck = idx this.deleteList.push(id) this.deleteListener = true }, deleteNotes() { this.deleteListener = false if (this.deleteList.length > 0) { for (let i in this.deleteList) { this.rdbStore.deleteValue('NOTES', 'ID', this.deleteList[i]) } this.deleteList.splice(0) setTimeout(() => { //---采用延时查询,避免数据不同步 this.query() }, 400) } },

5、查

查询操作:

主要涉及到一些简单的UI交互,只需调用封装好的search接口即可,不多解释,直接放图。

总结

官方提供的关系型数据库接口方便了很多没深入学习sql语句的鸿蒙开发者。我在这次的开发尝试当中也发现了一些坑,比如文档function back(options?: RouterOptions ):void写着router.back()同样支持params传参但是获取不到,或者说我操作不当,但是这方面的文档说明不太够,有点懵。但总之,关系型数据库用起来感觉很顺畅那就够了,没有被什么不知名bug绊住。

​​想了解更多关于开源的内容,请访问:​​

​​ 开源基础软件社区​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:什么是高可用性?高可用集群概念及工作原理详解
下一篇:什么是数据安全性?数据安全防护包括哪些方面?
相关文章