Notes
Notes - notes.io |
// File: entry/src/main/ets/common/KVStoreService.ts
// Demo: Persisting KV Store Data on HarmonyOS using ArkTS (Stage Model)
// ===============================
import { distributedKVStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { UIAbilityContext } from '@kit.AbilityKit';
/**
* Thin wrapper around callback-style ArkData Distributed KV APIs using Promises.
* Handles: createKVManager, getKVStore, put/get/delete, close/delete store.
* NOTE: Do NOT mutate UI directly inside callbacks; call from UI with await.
*/
export class KVStoreService {
private kvManager?: distributedKVStore.KVManager;
private kvStore?: distributedKVStore.SingleKVStore;
private appId = '';// bundleName
private storeId = '';
/** Initialize manager once with app context + bundle. */
init(context: UIAbilityContext, bundleName: string) {
if (this.kvManager) { return; }
this.appId = bundleName;
const cfg: distributedKVStore.KVManagerConfig = { context, bundleName };
try {
this.kvManager = distributedKVStore.createKVManager(cfg);
console.info('[KV] KVManager created');
} catch (e) {
const err = e as BusinessError;
console.error(`[KV] createKVManager failed code=${err.code} msg=${err.message}`);
throw err;
}
}
/** Open (or create) a SINGLE_VERSION device-local KV store. */
async openStore(storeId: string): Promise<void> {
if (!this.kvManager) throw new Error('KVManager not initialized');
this.storeId = storeId;
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: true,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S3,
};
await new Promise<void>((resolve, reject) => {
try {
this.kvManager!.getKVStore<distributedKVStore.SingleKVStore>(storeId, options,
(err, store) => {
if (err) { reject(err); return; }
this.kvStore = store;
console.info('[KV] Store opened');
resolve();
});
} catch (e) {
reject(e);
}
});
}
/** Close current store. */
async closeStore(): Promise<void> {
if (!this.kvManager || !this.storeId) return;
await new Promise<void>((resolve, reject) => {
try {
this.kvStore = undefined;
this.kvManager!.closeKVStore(this.appId, this.storeId, (err: BusinessError) => {
if (err) { reject(err); return; }
console.info('[KV] Store closed');
resolve();
});
} catch (e) { reject(e); }
});
}
/** Permanently delete current store. */
async deleteStore(): Promise<void> {
if (!this.kvManager || !this.storeId) return;
await new Promise<void>((resolve, reject) => {
try {
this.kvStore = undefined;
this.kvManager!.deleteKVStore(this.appId, this.storeId, (err: BusinessError) => {
if (err) { reject(err); return; }
console.info('[KV] Store deleted');
resolve();
});
} catch (e) { reject(e); }
});
}
/** Put string/number/boolean/bytes. */
async put(key: string, value: string | number | boolean | Uint8Array): Promise<void> {
const store = this.requireStore();
this.validateKeyValue(key, value);
await new Promise<void>((resolve, reject) => {
try {
store.put(key, value, (err) => {
if (err) { reject(err); return; }
resolve();
});
} catch (e) { reject(e); }
});
}
/** Get by key. */
async get(key: string): Promise<string | number | boolean | Uint8Array | undefined> {
const store = this.requireStore();
return await new Promise((resolve, reject) => {
try {
store.get(key, (err, data) => {
if (err) { reject(err); return; }
resolve(data);
});
} catch (e) { reject(e); }
});
}
/** Delete by key. */
async delete(key: string): Promise<void> {
const store = this.requireStore();
await new Promise<void>((resolve, reject) => {
try {
store.delete(key, (err) => {
if (err) { reject(err); return; }
resolve();
});
} catch (e) { reject(e); }
});
}
/** Helpers */
private requireStore(): distributedKVStore.SingleKVStore {
if (!this.kvStore) throw new Error('KV store not opened');
return this.kvStore as distributedKVStore.SingleKVStore;
}
/** Constraints: key <= 1KB(=1024 bytes), value <= 4MB. */
private validateKeyValue(key: string, value: unknown) {
const keyBytes = utf8ByteLength(key);
if (keyBytes > 1024) throw new Error(`Key exceeds 1KB: ${keyBytes} bytes`);
let valueBytes = 0;
if (value instanceof Uint8Array) valueBytes = value.byteLength;
else if (typeof value === 'string') valueBytes = utf8ByteLength(value);
else if (typeof value === 'number' || typeof value === 'boolean') valueBytes = 8; // rough
if (valueBytes > 4 * 1024 * 1024) throw new Error(`Value exceeds 4MB: ${valueBytes} bytes`);
}
}
export function utf8ByteLength(str: string): number {
let bytes = 0;
for (let i = 0; i < str.length; i++) {
const codePoint = str.charCodeAt(i);
if (codePoint <= 0x7F) bytes += 1;
else if (codePoint <= 0x7FF) bytes += 2;
else if (codePoint >= 0xD800 && codePoint <= 0xDBFF) { // surrogate pair
i++; bytes += 4;
} else bytes += 3;
}
return bytes;
}
export const kvStoreService = new KVStoreService();
// ===============================
// File: entry/src/main/ets/ability/EntryAbility.ts
// ===============================
import { UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { kvStoreService } from '../common/KVStoreService';
export default class EntryAbility extends UIAbility {
onCreate(want: Want): void {
// Create KVManager here once with app context and bundleName
kvStoreService.init(this.context, this.context.bundleName);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/KvStoreDemo', (err, data) => {
if (err) {
console.error('Failed to load content: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading content.');
});
}
}
// ===============================
// File: entry/src/main/ets/pages/KvStoreDemo.ets
// Simple UI to Put / Get / Delete keys; also Close/Delete store.
// ===============================
import { kvStoreService, utf8ByteLength } from '../common/KVStoreService';
@Entry
@Component
struct KvStoreDemo {
@State storeId: string = 'demo_store';
@State keyInput: string = 'username';
@State valueInput: string = 'aziz';
@State lastResult: string = '';
@State storeOpen: boolean = false;
async aboutToAppear() {
await this.ensureOpened();
}
private async ensureOpened() {
if (this.storeOpen) return;
try {
await kvStoreService.openStore(this.storeId);
this.lastResult = `Opened store: ${this.storeId}`;
this.storeOpen = true;
} catch (e) {
this.lastResult = `Open failed: ${JSON.stringify(e)}`;
}
}
build() {
Column() {
Text('KV Store Persist Demo').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 12 })
Text(`Store: ${this.storeId} — ${this.storeOpen ? 'OPEN' : 'CLOSED'}`)
.fontSize(14).margin({ bottom: 8 })
// Store controls
Row() {
Button(this.storeOpen ? 'Close Store' : 'Open Store', async () => {
if (this.storeOpen) {
try { await kvStoreService.closeStore(); this.storeOpen = false; this.lastResult = 'Store closed'; }
catch (e) { this.lastResult = `Close failed: ${JSON.stringify(e)}`; }
} else {
await this.ensureOpened();
}
}).margin(6)
Button('Delete Store', async () => {
try { await kvStoreService.deleteStore(); this.storeOpen = false; this.lastResult = 'Store deleted'; }
catch (e) { this.lastResult = `Delete store failed: ${JSON.stringify(e)}`; }
}).margin(6)
}
// Inputs
Text('Key (<= 1KB)').fontSize(16).margin({ top: 16, bottom: 4 })
TextInput({ text: this.keyInput })
.onChange(v => this.keyInput = v)
.placeholder('key')
.margin({ bottom: 8 })
Text('Value (string; <= 4MB)').fontSize(16).margin({ bottom: 4 })
TextInput({ text: this.valueInput })
.onChange(v => this.valueInput = v)
.placeholder('value')
.margin({ bottom: 12 })
// Actions
Row() {
Button('PUT', async () => {
try {
const keyBytes = utf8ByteLength(this.keyInput);
if (keyBytes > 1024) throw new Error(`Key too large: ${keyBytes} bytes`);
await kvStoreService.put(this.keyInput, this.valueInput);
this.lastResult = `PUT ok key="${this.keyInput}"`;
} catch (e) {
this.lastResult = `PUT failed: ${JSON.stringify(e)}`;
}
}).margin(6)
Button('GET', async () => {
try {
const data = await kvStoreService.get(this.keyInput);
this.lastResult = `GET ok value=${data === undefined ? 'undefined' : data}`;
} catch (e) {
this.lastResult = `GET failed: ${JSON.stringify(e)}`;
}
}).margin(6)
Button('DELETE', async () => {
try { await kvStoreService.delete(this.keyInput); this.lastResult = 'DELETE ok'; }
catch (e) { this.lastResult = `DELETE failed: ${JSON.stringify(e)}`; }
}).margin(6)
}
// Result / Logs
Text('Result / Log').fontSize(16).margin({ top: 16, bottom: 4 })
Text(this.lastResult).fontSize(14).maxLines(8).textOverflow({ overflow: TextOverflow.Ellipsis })
.backgroundColor('#1f1f1f').fontColor('#f1f1f1').padding(12).borderRadius(8)
// Hints
Divider().margin({ top: 16, bottom: 8 })
Text('Notes: DO NOT block UI inside KV callbacks. Use async/await from UI actions. Max 16 stores open per app; key <= 1KB; value <= 4MB.')
.fontSize(12)
.fontColor('#666')
}
.padding(16)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
}
// ===============================
// (Optional) File: entry/src/main/module.json5 – ensure you have this page route
// {
// "module": {
// "abilities": [
// {
// "name": ".EntryAbility",
// "srcEntry": "./ets/ability/EntryAbility.ts",
// "pages": "./ets/pages/index",
// "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ]
// }
// ]
// }
// }
// and in pages/index.ets: export default { pages: ['KvStoreDemo'] }
![]() |
Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...
With notes.io;
- * You can take a note from anywhere and any device with internet connection.
- * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
- * You can quickly share your contents without website, blog and e-mail.
- * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
- * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.
Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.
Easy: Notes.io doesn’t require installation. Just write and share note!
Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )
Free: Notes.io works for 14 years and has been free since the day it was started.
You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;
Email: [email protected]
Twitter: http://twitter.com/notesio
Instagram: http://instagram.com/notes.io
Facebook: http://facebook.com/notesio
Regards;
Notes.io Team
