| name | harmonyos-development |
| description | HarmonyOS 6.1 / 6.1.1 / NEXT (鸿蒙) comprehensive development knowledge — ArkTS, ArkUI, Stage model, 60+ Kit APIs, UI components, state management, navigation, animation, networking, data persistence, media, concurrency, testing, security, performance optimization, third-party libraries. 4200+ lines, 237 sections, 105+ code examples.
Trigger: HarmonyOS, ArkTS, ArkUI, .ets, DevEco Studio, Stage model, UIAbility, @State, @Prop, @Link, @Provide, @Observed, @ObjectLink, @ComponentV2, @ObservedV2, @Trace, @Monitor, StateStore, NavPathStack, LazyForEach, @Reusable, animateTo, keyframeAnimateTo, geometryTransition, TaskPool, Worker, @Concurrent, @Sendable, module.json5, oh-package.json5, OHPM, sys.symbol, HAP, HSP, Tabs, TabContent, Swiper, WaterFlow, Grid, TextInput, TextArea, AlertDialog, openCustomDialog, openBindSheet, bindContentCover, Refresh, swipeAction, ListItemGroup, relationalStore, preferences, fileIo, DocumentViewPicker, photoAccessHelper, notificationManager, backgroundTaskManager, abilityAccessCtrl, pasteboard, WebSocket, connection, request.agent, startAbilityByType, expandSafeArea, KeyboardAvoidMode, setPreferredOrientation, colorMode, registerFont, hitTestBehavior, priorityGesture, onVisibleAreaChange, EventHub, arkxtest, BlurStyle, systemMaterialEffect, ArkGuard, 代码混淆, CameraKit, cameraPicker, AudioRenderer, AudioKit, AVPlayer, AVRecorder, ImageKit, PixelMap, ScanKit, scanBarcode, customScan, AccountKit, PaymentKit, PushKit, MapKit, ShareKit, systemShare, AppLinking, domainVerify, CoreVisionKit, textRecognition, AVSessionKit, FormKit, FormExtensionAbility, WeatherServiceKit, weatherService, PenKit, HandwriteController, AppStorageV2, PersistenceV2, @Local, @Param, @Event, @Once, @Provider, @Consumer, axios, @ohos/axios, pulltorefresh, lottie, imageknife, dayjs, API 22, API 23, API 24, HarmonyOS 6.1, HarmonyOS 6.1.1, Follow the Person, anchorPosition, continuable, onContinue, 应用接续, 扫码, 深链接, 分享, 剪贴板, 天气, 手写笔, 鸿蒙, 鸿蒙开发, 鸿蒙NEXT, 方舟语言, 状态管理, 导航路由, 懒加载, 组件复用, 鸿蒙权限, 鸿蒙测试, 液态玻璃, 沉浸光感, 折叠屏, 鸿蒙入门, 沉浸式, 深色模式, 下拉刷新, 上拉加载, 左滑删除, 瀑布流, 轮播图, 底部导航, 软键盘, 横竖屏, 自定义字体, 桌面快捷方式, 手势冲突, 文件选择, 网络请求, 后台下载, 弹窗
|
HarmonyOS (鸿蒙) Development
Covers HarmonyOS 6.1 (API 23, stable) / 6.1.1 (API 24, beta) / NEXT native app development — the Huawei mobile OS family that runs independently of Android (AOSP-free since HarmonyOS NEXT, released 2024). Primary language is ArkTS (a strict, statically-checked superset of TypeScript) and the primary UI framework is ArkUI (declarative, state-driven). HarmonyOS 6.0+ adds system-level "Immersive Light Perception" visual effects (液态玻璃/沉浸光感视效); HarmonyOS 6.1.1 introduces "Follow the Person" camera tracking and AI super frame.
Platform snapshot
| Item | Value |
|---|
| OS | HarmonyOS 6.1 (stable, released 2026/04/20, API 23). HarmonyOS 6.1.1 (API 24) in developer beta. Pure HarmonyOS, AOSP-free |
| Language | ArkTS (primary), Cangjie (beta), C/C++ via NAPI |
| UI framework | ArkUI declarative (ArkUI-X for cross-platform) |
| Compiler | ArkCompiler — AOT to native machine code; LiteActor concurrency |
| Package manager | ohpm — oh-package.json5; registry at DevEco Service (OHPM Central) |
| IDE | DevEco Studio 6.1+ (supports API 23/24, Hvigor 6.22+, IntelliJ-based) |
| App model | Stage model (FA model is legacy — don't use in new apps) |
| Packaging | HAP (entry/feature), HSP (shared package), HAR (static archive), atomic .app |
| Recommended API | API 22+ (use API 23 for new projects; API 24 for camera/AI-heavy apps) |
| Sample catalog | https://developer.huawei.com/consumer/cn/samples/ |
Release timeline (recent):
- HarmonyOS 6.0.1(21) — 2025/11/25 (initial stable with Mate 80 series)
- HarmonyOS 6.0.2(22) — 2026/01/23 (incremental update)
- HarmonyOS 6.0.0.328 Pollen Beta(23) — 2026/02/28 (closed beta, 25 models)
- HarmonyOS 6.1(23) — 2026/04/20 (stable general release)
- HarmonyOS 6.1.1(24) Beta 1 — 2026/05 (developer beta, camera/AI focus)
What's new in API 23 (HarmonyOS 6.1)
ArkUI enhancements:
Navigation supports binding the routing stack to the component itself and specifying a NavDestination as the navigation bar (home page) — no more separate root container needed
Menu adds anchorPosition property: control popup position relative to upper-left of anchor with horizontal/vertical offsets
Image component improved SVG parsing capabilities
- Batch of new C APIs for attribute styles (Native side)
New C-side capabilities (Native/NAPI):
- UDMF (Unified Data Management Framework) C APIs
- Component drag-and-drop C APIs
- Cryptographic algorithm C APIs
Data:
relationalStore enhanced sendable function — better cross-thread data passing
Graphics & AI:
- AI super frame feature in Graphics Accelerate Kit (frame interpolation for smoother animations)
ArkWeb: further capability enhancements (intercept/cookies)
What's new in API 24 (HarmonyOS 6.1.1 Beta)
Project layout (Stage model)
MyApp/
├─ AppScope/
│ ├─ app.json5 # global app config (bundleName, icon, label)
│ └─ resources/ # shared resources
├─ entry/ # main HAP (entry module)
│ ├─ src/main/
│ │ ├─ ets/
│ │ │ ├─ entryability/EntryAbility.ets # UIAbility subclass
│ │ │ ├─ entrybackupability/
│ │ │ └─ pages/Index.ets # ArkUI pages
│ │ ├─ resources/ # strings, colors, media, profiles
│ │ └─ module.json5 # module config, abilities, permissions
│ └─ build-profile.json5
├─ oh-package.json5 # ohpm dependencies
└─ build-profile.json5 # project-level
The Stage model — core concepts
Component types
- UIAbility — has UI, handles user interaction. Entry points. One instance per task.
- ExtensionAbility — background / extension scenarios:
ServiceExtensionAbility — background services (system apps)
FormExtensionAbility — home-screen widget (服务卡片)
WorkSchedulerExtensionAbility — deferred tasks
InputMethodExtensionAbility, WallpaperExtensionAbility, BackupExtensionAbility, etc.
- AbilityStage — module-level lifecycle container (one per HAP)
- WindowStage — window container scoped to a UIAbility
UIAbility lifecycle
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
onDestroy(): void { }
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => { });
}
onWindowStageDestroy(): void { }
onForeground(): void { }
onBackground(): void { }
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
}
Launching another ability
import { common, Want } from '@kit.AbilityKit';
const ctx = getContext(this) as common.UIAbilityContext;
const want: Want = {
bundleName: 'com.example.app',
abilityName: 'DetailAbility',
parameters: { id: 42 }
};
ctx.startAbility(want);
module.json5 essentials
{
"module": {
"name": "entry",
"type": "entry", // entry | feature | shared
"srcEntry": "./ets/entryability/EntryAbility.ets",
"deviceTypes": ["phone", "tablet", "2in1"],
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"skills": [{
"actions": ["action.system.home"],
"entities": ["entity.system.home"]
}]
}],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
ArkTS language — strictness rules
ArkTS = TypeScript MINUS dynamic patterns, PLUS stricter static typing for AOT perf.
Prohibited / changed vs TS:
- No
any / unknown as everyday escape hatches — declare real types
- No dynamic property add/delete on objects (
obj.foo = bar on untyped object is a compile error)
- No
Function.prototype.bind, call, apply with reshaped this
- No structural typing shortcuts — use nominal classes / interfaces
- No
Object.keys/Object.assign for arbitrary reshaping
Record<K,V> for true dictionary maps
- Use
class / interface with explicit fields
class User { constructor(public id: number, public name: string) {} }
Naming conventions (official coding style guide)
| Element | Convention | Example |
|---|
| Classes, Structs, Enums | UpperCamelCase | PersonInfo, ColorType |
| Variables, Parameters, Methods | lowerCamelCase | userName, getUserInfo() |
| Constants | UPPER_SNAKE_CASE | MAX_VALUE, DEFAULT_TIMEOUT |
| Boolean variables | Prefix with is, has, can | isVisible, hasPermission |
Formatting: 2-space indent, max 120 chars/line, K&R braces, always use {} for if/for/while.
High-performance ArkTS rules (from official best practices)
- Use
const for unchanging values — enables engine optimization
- Never mix int and float in same variable —
let n = 1; n = 1.1; causes boxing overhead
- Use TypedArrays (
Int8Array, Float32Array) for numerical computation
- Avoid sparse arrays —
arr[9999] = 0 forces hash-table storage (much slower)
- Don't mix types in arrays —
[1, "a", 2] deoptimizes
- Cache property lookups outside hot loops
- Avoid exception throwing in perf-critical loops — use sentinel values
- Minimize closures in hot paths — pass variables via function params instead
- Use Array methods (
forEach, map, filter, reduce) — internally optimized
- Keep
build() pure and declarative — no side effects, load data in aboutToAppear()
- Use
HashMap instead of Record for key-value operations — faster lookup/insert
- Reduce multi-level indirect exports — prefer direct
export { foo } from './module'
- Use lazy import (
import lazy { Foo } from './heavy') for modules not needed at startup
ArkUI — declarative UI
Custom component basics
@Entry
@Component
struct Index {
@State count: number = 0;
build() {
Column({ space: 12 }) {
Text(`Count: ${this.count}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('Increment')
.onClick(() => { this.count++; })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
Component lifecycle callbacks
All @Component:
| Callback | When | Notes |
|---|
aboutToAppear() | After created, BEFORE build() | Can change state here; changes apply in first build() |
onDidBuild() | After build() completes | Do NOT change state or call animateTo(). API 12+ |
aboutToDisappear() | Before destruction | Do NOT change state (especially @Link). No async/await |
aboutToReuse(params) | Reusable component re-added from cache | Update state with new params. API 10+ |
aboutToRecycle() | Component moving to reuse cache | Release heavy resources. API 10+ |
Only @Entry:
| Callback | When |
|---|
onPageShow() | Each time page is displayed |
onPageHide() | Each time page is hidden |
onBackPress(): boolean | User taps Back. Return true to override default |
Execution order (cold start):
Parent aboutToAppear → Parent build → Parent onDidBuild → Child aboutToAppear → Child build → Child onDidBuild → onPageShow
Layout containers
| Container | When to use | Performance |
|---|
Column / Row | Linear arrangement | Best — single-pass layout |
Stack | Overlapping / stacking | Good |
Flex | Items need stretch/shrink | Slower — extra pass for flexGrow/flexShrink |
RelativeContainer | Complex layouts, avoid deep nesting | Good — flat structure |
GridRow / GridCol | Responsive multi-device grids | Good |
List | Scrollable list with recycling | Best for long lists (with LazyForEach) |
Column/Row alignment:
- Main axis:
justifyContent(FlexAlign.Start | .Center | .End | .SpaceBetween | .SpaceAround | .SpaceEvenly)
- Cross axis: Column →
alignItems(HorizontalAlign.Start | .Center | .End), Row → alignItems(VerticalAlign.Top | .Center | .Bottom)
Stack: alignContent takes 9 positions (TopStart, Top, TopEnd, Start, Center, End, BottomStart, Bottom, BottomEnd). zIndex controls layer order.
Flex vs Column/Row: Flex requires re-layout for flexShrink/flexGrow. Always prefer Column/Row when you don't need flex behavior.
RelativeContainer: Use __container__ as anchor ID for the container itself. Each child needs .id(). Set .alignRules({ top: { anchor: 'id', align: VerticalAlign.Bottom } }).
Blank(): Fills remaining space in Row/Column — use for "label ... value" layouts: Row() { Text('Name'); Blank(); Text('Value') }.
displayPriority: Lower-priority children auto-hide when container shrinks. Row() { A().displayPriority(1); B().displayPriority(3); C().displayPriority(1) } — A and C hide first.
layoutWeight: Proportional sizing — Row() { Column().layoutWeight(1); Column().layoutWeight(2) } gives 1:2 ratio.
AttributeModifier — reusable style object:
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.width('100%').height(48).fontSize(16).fontColor(Color.White).backgroundColor('#007DFF');
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#0056B3');
}
}
Button('Submit').attributeModifier(new PrimaryButtonModifier())
Performance-critical patterns
LazyForEach for large lists (only renders visible items):
class MyDataSource implements IDataSource {
private data: string[] = []
totalCount(): number { return this.data.length }
getData(index: number): string { return this.data[index] }
registerDataChangeListener(listener: DataChangeListener): void { }
unregisterDataChangeListener(listener: DataChangeListener): void { }
}
List() {
LazyForEach(this.dataSource, (item: string, index: number) => {
ListItem() { Text(item) }
}, (item: string) => item)
}
.cachedCount(5)
@Reusable components (69% faster component creation):
@Reusable
@Component
struct MyListItem {
@State message: Message = new Message('default')
aboutToReuse(params: Record<string, ESObject>) {
this.message = params.message as Message
}
aboutToRecycle() { }
build() { Text(this.message.value) }
}
Rules: only works within same parent; don't nest @Reusable inside @Reusable; combine with LazyForEach.
Layout performance rules:
- Max 3 levels of nesting — each level adds layout cost
- Use
if/else over .visibility() — hidden components still participate in layout
- Use
RelativeContainer to flatten deep Row/Column/Flex hierarchies (documented 26% improvement)
- Set explicit dimensions on
List inside Scroll — without them ALL children load at once
- Avoid
@StorageLink for frequently-changing data — propagates to all subscribers
onVisibleAreaChange — trigger logic when component enters/leaves viewport:
Image(item.url)
.onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
if (isVisible && currentRatio >= 1.0) { this.loadHighRes(); }
if (!isVisible) { this.releaseImage(); }
})
Use cases: lazy image loading, video auto-play/pause on scroll, exposure analytics.
Animation
Explicit animation (animateTo) — state changes in closure animate:
this.getUIContext()?.animateTo({
duration: 300,
curve: Curve.EaseOut,
onFinish: () => { console.info('done') }
}, () => {
this.width = 200
})
Property animation (.animation()) — implicit, applied to preceding attributes:
Text('Hello')
.width(this.myWidth)
.animation({ duration: 500, curve: Curve.EaseIn })
Curve values: Curve.Linear | .Ease | .EaseIn | .EaseOut | .EaseInOut | .FastOutSlowIn | .Friction | .Sharp | .Smooth
Spring curves (string): 'spring(velocity,mass,stiffness,damping)', 'springMotion(response,dampingFraction)', 'responsiveSpringMotion(response,dampingFraction)'
Shared element transition:
Image($r('app.media.photo')).geometryTransition('picture')
this.getUIContext()?.animateTo({ duration: 300 }, () => { this.isExpanded = !this.isExpanded })
Keyframe animation (keyframeAnimateTo) — multi-step sequences:
this.getUIContext()?.keyframeAnimateTo({ iterations: 2 }, [
{ duration: 100, event: () => { this.translateX = 10; } },
{ duration: 100, event: () => { this.translateX = -10; } },
{ duration: 100, event: () => { this.translateX = 0; } },
]);
Animation performance tips: Prefer transform properties (scale/translate/rotate/opacity) over layout properties (width/height/margin) — transforms skip re-layout.
Tabs — bottom/top navigation
@Entry @Component
struct MainPage {
@State currentIndex: number = 0;
@Builder tabBuilder(index: number, title: string, icon: Resource) {
Column() {
SymbolGlyph(icon).fontSize(24)
.fontColor([this.currentIndex === index ? '#007DFF' : '#99000000'])
Text(title).fontSize(10).margin({ top: 4 })
.fontColor(this.currentIndex === index ? '#007DFF' : '#99000000')
}.justifyContent(FlexAlign.Center).height('100%').width('100%')
}
build() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() { HomePage() }
.tabBar(this.tabBuilder(0, 'Home', $r('sys.symbol.house')))
TabContent() { MinePage() }
.tabBar(this.tabBuilder(1, 'Me', $r('sys.symbol.person')))
}
.barHeight(56)
.onChange((index) => { this.currentIndex = index; })
.scrollable(false)
}
}
Glass-blur tab bar: .barOverlap(true).barBackgroundBlurStyle(BlurStyle.Thin).
Swiper — carousel / banner
Swiper() {
ForEach(this.banners, (item: BannerItem) => {
Image(item.url).width('100%').height(180).borderRadius(12)
})
}
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(new DotIndicator()
.color('#33000000').selectedColor('#007DFF')
.itemWidth(8).selectedItemWidth(16))
WaterFlow — Pinterest-style layout
WaterFlow({ scroller: this.scroller }) {
LazyForEach(this.dataSource, (item: CardItem) => {
FlowItem() {
Column() {
Image(item.image).width('100%').borderRadius(8)
Text(item.title).fontSize(14).padding(8)
}
}
}, (item: CardItem) => item.id)
}
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.cachedCount(10)
Grid — fixed grid layout
Grid() {
ForEach(this.items, (item: GridItemData) => {
GridItem() {
Column() {
Image(item.icon).width(40).height(40)
Text(item.name).fontSize(12).margin({ top: 4 })
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.height(200)
TextInput / TextArea
TextInput({ placeholder: 'Enter username' })
.type(InputType.Normal)
.maxLength(20)
.onChange((value: string) => { this.username = value; })
.onSubmit((enterKey: EnterKeyType) => { })
TextArea({ placeholder: 'Enter description', text: $$this.desc })
.maxLength(200)
.showCounter(true)
Two-way binding with $$: TextInput({ text: $$this.value }) — no onChange needed.
Router — basic page navigation
import { router } from '@kit.ArkUI';
router.pushUrl({
url: 'pages/Detail',
params: { id: '123', title: 'Hello' }
});
const params = router.getParams() as Record<string, string>;
router.back();
router.replaceUrl({ url: 'pages/Login' });
Note: For Navigation-based apps, prefer NavPathStack.pushPath() over Router.
AlertDialog / Toast
AlertDialog.show({
title: 'Confirm',
message: 'Delete this item?',
primaryButton: { value: 'Cancel', action: () => {} },
secondaryButton: { value: 'Delete', fontColor: Color.Red,
action: () => { this.deleteItem(); }
},
});
this.getUIContext().getPromptAction().showToast({
message: 'Operation successful',
duration: 2000,
});
Common form components — quick reference
| Component | Key Props | Example |
|---|
Checkbox | select, onChange((val: boolean) => {}) | Checkbox({ name: 'agree' }).select(this.agreed) |
Toggle | type(ToggleType.Switch), isOn, onChange | Toggle({ type: ToggleType.Switch, isOn: $$this.on }) |
Radio | value, group, checked, onChange | Radio({ value: 'male', group: 'gender' }).checked(true) |
Select | options: SelectOption[], selected, value, onSelect | Select([{value:'A'},{value:'B'}]).selected(0) |
Slider | value, min, max, step, onChange | Slider({ value: $$this.val, min: 0, max: 100 }) |
DatePicker | start, end, selected, onChange | DatePicker({ selected: this.date }).onChange((v) => {}) |
TimePicker | selected, useMilitaryTime, onChange | TimePicker({ selected: this.time }) |
Search | value, placeholder, onSubmit, onChange | Search({ value: $$this.keyword, placeholder: 'Search' }) |
Progress | value, total, type(ProgressType.Linear) | Progress({ value: 60, total: 100 }) |
LoadingProgress | — | LoadingProgress().width(48).color('#007DFF') |
EventHub — UIAbility ↔ page communication
this.context.eventHub.emit('dataReady', { items: [...] });
const context = getContext(this) as common.UIAbilityContext;
context.eventHub.on('dataReady', (data: Record<string, ESObject>) => {
this.items = data.items;
});
context.eventHub.off('dataReady');
HarmonyOS 6.0 visual effects (沉浸光感视效 / 液态玻璃)
HarmonyOS 6.0 (API 23) introduces system-level "Immersive Light Perception" visual effects. Users enable via Settings → Desktop & Personalization → Immersive Light Effect (强/均衡/弱). Developers achieve similar effects through these ArkUI attributes:
BlurStyle enum (API 9–11):
| Name | Since | Level |
|---|
Thin / Regular / Thick | API 9 | Material blur |
BACKGROUND_THIN / BACKGROUND_REGULAR / BACKGROUND_THICK / BACKGROUND_ULTRA_THICK | API 10 | Depth-of-field (min→max) |
COMPONENT_ULTRA_THIN / COMPONENT_THIN / COMPONENT_REGULAR / COMPONENT_THICK / COMPONENT_ULTRA_THICK | API 11 | Component-level material |
NONE | API 10 | No blur |
backgroundBlurStyle (API 9+):
Column() { }
.backgroundBlurStyle(BlurStyle.Thin, {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.DEFAULT,
scale: 1.0
})
foregroundBlurStyle (API 10+):
Image($r('app.media.photo'))
.foregroundBlurStyle(BlurStyle.Regular)
backgroundEffect (API 11+) — fine-grained control:
Column() { }
.backgroundEffect({
radius: 20,
saturation: 15,
brightness: 0.6,
color: '#80FFFFFF'
})
blur / backdropBlur (API 7+) — numeric radius:
Column() { }
.backdropBlur(20, { grayscale: [30, 50] })
.blur(10)
backgroundBrightness (API 12+):
Column() { }
.backgroundBrightness({ rate: 0.5, lightUpDegree: 0.2 })
Visual effect filters (API 12+):
import { uiEffect } from '@kit.ArkGraphics2D';
const blurFilter = uiEffect.createFilter().blur(10);
Column() { }
.backgroundFilter(blurFilter)
.foregroundFilter(blurFilter)
pointLight (API 11+, System API only — NOT available for third-party apps):
Flex()
.pointLight({
lightSource: { positionX: '50%', positionY: '50%', positionZ: 80, intensity: 2, color: Color.White },
illuminated: IlluminatedType.BORDER,
bloom: 0.5
})
Up to 12 light sources can illuminate a single component. HarmonyOS 6.0 adds dual-edge flow light and UV background flow light effects.
systemMaterialEffect (HDS layer, API 23+, HarmonyOS-only SDK):
import { hdsMaterial } from '@kit.ArkUI';
Column() { }
.systemMaterialEffect({
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
})
Note: hdsMaterial is part of the closed-source HarmonyOS Design System (HDS), not OpenHarmony. Requires HarmonyOS 6.0 SDK (API 23+).
State-management decorators
| Decorator | Scope | Purpose |
|---|
@State | within component | Owned mutable state; triggers re-render |
@Prop | parent → child | One-way copy (child has local copy) |
@Link | parent ↔ child | Two-way binding (use $var to pass) |
@Provide / @Consume | ancestor → descendant | Cross-level implicit binding by key |
@Observed (class) + @ObjectLink (prop) | class instances | Observe changes to class properties |
@Watch('handler') | any of above | Callback on value change |
@StorageLink / @StorageProp | app-wide AppStorage | Global reactive state |
@LocalStorageLink / @LocalStorageProp | page-scoped | Scoped reactive state |
Passing @Link:
@Entry @Component struct Parent {
@State val: number = 0;
build() { Child({ val: $val }) }
}
@Component struct Child {
@Link val: number;
build() { Button(`${this.val}`).onClick(() => this.val++) }
}
Observing class objects:
@Observed class Task { constructor(public title: string, public done: boolean) {} }
@Component struct TaskRow {
@ObjectLink task: Task;
build() { Text(this.task.title) }
}
Arrays of @Observed instances require @ObjectLink in the row component — parent @State tasks: Task[] only reacts to array mutations (push/splice/reassign), not per-item changes.
State management performance rules (from official docs):
- Minimize state scope: only
@State variables that directly affect UI
@Prop creates deep copy on every update — for large objects, prefer @Link (by reference) or @ObjectLink
@Link is preferred for inter-component communication — avoids unnecessary re-renders
@Observed + @ObjectLink for nested objects — fine-grained property observation
@ObjectLink is READ-ONLY — cannot reassign whole object (this.task = new Task() breaks binding)
- Avoid
@StorageLink for frequently-changing data — global state changes propagate to ALL subscribers
- Observation depth (V1):
@State/@Prop/@Link observe ONLY first-level properties. Nested changes are NOT detected. Array: only push/splice/reassign/length, NOT item mutations.
V2 state decorators (API 12+, stable since API 23 — recommended for new code)
V2 decorators have graduated from experimental to stable as of HarmonyOS 6.1 (API 23). Official recommendation: migrate to V2 for new projects.
| V1 | V2 replacement | Change |
|---|
@Component | @ComponentV2 | Clearer semantics |
@State | @Local | Cannot be initialized externally — internal state only |
@Prop | @Param + @Once | Read-only inputs; @Once for one-time init |
@Link | @Param + @Event | Two-way: input via @Param, output via callback @Event |
@Observed + @ObjectLink | @ObservedV2 + @Trace | Deep observation across multiple nested levels |
@Watch | @Monitor('prop') | More precise deep listener |
AppStorage | AppStorageV2 | Unified with @ObservedV2 + @Trace |
| (none) | PersistenceV2 | Persistent storage with V2 observation; auto-saved to disk |
@Provide / @Consume | @Provider() / @Consumer() | Renamed; same semantics |
@ObservedV2
class UserInfo {
@Trace name: string = '';
@Trace age: number = 0;
address: string = '';
}
Rules: @ObservedV2 and @Trace must be used together (either alone has no effect). Only @Trace-decorated properties participate in UI rendering.
AppStorageV2 — global reactive state:
import { AppStorageV2 } from '@kit.ArkUI';
@ObservedV2
class UserState { @Trace name: string = 'Guest'; }
const user = AppStorageV2.connect(UserState, 'user', () => new UserState())!;
@ComponentV2
struct Header {
user: UserState = AppStorageV2.connect(UserState, 'user')!;
build() { Text(this.user.name) }
}
PersistenceV2 — auto-persisted state (survives app restart):
import { PersistenceV2, Type } from '@kit.ArkUI';
@ObservedV2
class Settings {
@Trace @Type(String) theme: string = 'light';
@Trace @Type(Number) fontSize: number = 14;
}
const settings = PersistenceV2.connect(Settings, 'app_settings', () => new Settings())!;
PersistenceV2.notifyOnError((key, reason, msg) => { console.error(reason, msg); });
@Type decorator is required for PersistenceV2 to serialize correctly.
StateStore — global state management (2026, officially recommended for mid-large apps)
Separates state logic from UI components entirely. Works with @ObservedV2 + @Trace.
import { StateStore } from '@kit.ArkUI';
@ObservedV2
class CounterStore {
@Trace count: number = 0;
increment(): void {
this.count++;
}
}
const counterStore = StateStore.createStore(new CounterStore());
@Entry
@Component
struct CounterPage {
build() {
Column() {
Text(`Count: ${counterStore.getState().count}`)
Button('Add').onClick(() => {
counterStore.getState().increment();
})
}
}
}
When to use: Multiple pages/components share the same state; state logic is complex; need thread-safe updates (TaskPool workers can safely update StateStore).
Docs: https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store
Builders, styles, extends
@Builder function Label(text: string) { Text(text).fontSize(16) }
@Styles function Card() { .padding(12).borderRadius(12).backgroundColor('#FFF') }
@Extend(Text) function title() { .fontSize(22).fontWeight(FontWeight.Bold) }
Text('Hello').title()
Column() { Label('x') } .Card()
Navigation (recommended: Navigation component, not Router)
@Entry @Component struct App {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
build() {
Navigation(this.pathStack) {
Button('Go').onClick(() => this.pathStack.pushPath({ name: 'Detail', param: 42 }))
}
.navDestination((name: string, param: Object) => {
if (name === 'Detail') Detail({ id: param as number })
})
}
}
The older router module (@ohos.router) still works but is being phased out — Navigation + NavPathStack is the official replacement since API 12+. Huawei publishes a transition guide for migrating from router to Navigation. For new projects, always use Navigation; for legacy code, migrate when convenient.
NavPathStack full API:
pathStack.pushPath({ name: 'Page', param: data });
pathStack.pushPathByName('Page', data);
pathStack.pushPathByName('Page', data, (popInfo) => {
console.info('Pop result: ' + JSON.stringify(popInfo.result));
});
pathStack.pop();
pathStack.replacePath({ name: 'Page', param: data });
pathStack.removeIndex(0);
pathStack.movePageToTop('Page');
pathStack.getParamByIndex(index);
pathStack.getParamByName('Page');
pathStack.getAllPathName();
pathStack.size();
Route interception:
pathStack.setInterception({
willShow: (from, to, operation) => { },
didShow: (from, to, operation) => { }
});
Display modes: Stack (single column), Split (two columns), Auto (adaptive, 600vp threshold).
HarmonyOS Kits (common imports)
import { UIAbility, Want, common, abilityAccessCtrl } from '@kit.AbilityKit';
import { window, display, promptAction } from '@kit.ArkUI';
import { http, webSocket, connection } from '@kit.NetworkKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { geoLocationManager } from '@kit.LocationKit';
import { relationalStore, preferences, distributedKVStore } from '@kit.ArkData';
import { notificationManager } from '@kit.NotificationKit';
import { speechRecognizer } from '@kit.CoreSpeechKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
Full Kit catalog — official SDK categories (developer.huawei.com/consumer/cn/sdk/)
应用框架 Application Framework
| Kit | Import key | Purpose |
|---|
| Ability Kit | AbilityKit | UIAbility, ExtensionAbility, Want, context, routing |
| Accessibility Kit | AccessibilityKit | Screen reader, universal design, a11y services |
| ArkData | ArkData | relationalStore, preferences, distributedKVStore |
| ArkTS | — | Language spec & Ark compiler tooling |
| ArkUI | ArkUI | window, display, promptAction, Navigation, components |
| ArkWeb | ArkWeb | WebView / Web component embedding |
| Background Tasks Kit | BackgroundTasksKit | Deferred/scheduled background work, transient tasks |
| Core File Kit | CoreFileKit | fileIo, picker, sandbox paths |
| Form Kit | FormKit | FormExtensionAbility, home-screen service cards/widgets |
| IME Kit | IMEKit | Input method engine development |
| IPC Kit | IPCKit | Inter-process communication (Parcel, RemoteObject) |
| Localization Kit | LocalizationKit | i18n, l10n, RTL, pseudo-localization |
应用服务 Application Services
| Kit | Import key | Purpose |
|---|
| Account Kit | AccountKit | One-click Huawei ID login, OAuth 2.0 |
| Ads Kit | AdsKit | Advertising SDK (banner, native, interstitial) |
| App Linking Kit | AppLinkingKit | Deep links, deferred deep links |
| Calendar Kit | CalendarKit | Calendar events, reminders |
| Contacts Kit | ContactsKit | Contact read/write/search |
| IAP Kit | IAPKit | In-app purchases (consumable, non-consumable, subscription) |
| Live View Kit | LiveViewKit | Live activities on lock screen / notification center |
| Location Kit | LocationKit | geoLocationManager, geofencing, geocoding |
| Map Kit | MapKit | Huawei Maps SDK, routing, place search |
| Notification Kit | NotificationKit | Local + push notifications, badges |
| Payment Kit | PaymentKit | Huawei Pay transaction processing |
| Push Kit | PushKit | Push notification delivery service |
| Scan Kit | ScanKit | QR/barcode scanning |
| Share Kit | ShareKit | Cross-app content sharing |
| Weather Service Kit | WeatherServiceKit | Weather data (current/daily/hourly/alerts/indices/tides) |
| Pen Kit | Penkit | Stylus / handwriting component (M-Pencil devices) |
| Wear Engine | WearEngine | Phone↔watch communication, device discovery |
| Health Service Kit | HealthServiceKit | Health data services |
系统 System
| Kit | Import key | Purpose |
|---|
| Asset Store Kit | AssetStoreKit | Secure credential/secret storage |
| Basic Services Kit | BasicServicesKit | Battery, vibration, thermal, brightness, clipboard |
| Connectivity Kit | ConnectivityKit | Bluetooth, Wi-Fi, NFC, USB, hotspot |
| Crypto Architecture Kit | CryptoArchitectureKit | Encryption, key management, certificates |
| Distributed Service Kit | DistributedServiceKit | deviceManager, cross-device discovery |
媒体 Media
| Kit | Import key | Purpose |
|---|
| Audio Kit | AudioKit | Audio playback, recording, routing, focus |
| AVCodec Kit | AVCodecKit | Hardware-accelerated encode/decode (H.264, H.265, AAC…) |
| Camera Kit | CameraKit | Camera preview, photo capture, video recording |
| Image Kit | ImageKit | Image decoding, transformation, EXIF |
| Media Kit | MediaKit | AVPlayer, AVRecorder (unified playback/recording) |
| Media Library Kit | MediaLibraryKit | photoAccessHelper, media library CRUD |
| Scan Kit | ScanKit | QR/barcode scanning |
图形 Graphics
| Kit | Import key | Purpose |
|---|
| ArkGraphics 2D | ArkGraphics2D | 2D Canvas drawing, effects, blur, shadow |
| ArkGraphics 3D | ArkGraphics3D | 3D scene graph, glTF rendering |
| UI Design Kit | UIDesignKit | hdsDrawable icon processing, HdsNavigation component |
| XComponent | (ArkUI built-in) | Native OpenGL ES / Vulkan surface via NAPI |
AI
| Kit | Import key | Purpose |
|---|
| Core Speech Kit | CoreSpeechKit | On-device ASR / TTS engine |
| Core Vision Kit | CoreVisionKit | OCR, face detection, image segmentation, super-resolution |
| Intents Kit | IntentsKit | Intent recognition (30+ domains, 60+ built-in intents) |
| MindSpore Lite | MindSporeLiteKit | Edge model inference (Caffe/TF/ONNX/MindIR) |
| Natural Language Kit | NaturalLanguageKit | Word segmentation, NER, keyword extraction |
Performance & DevTools
| Kit | Import key | Purpose |
|---|
| Performance Analysis Kit | PerformanceAnalysisKit | hilog, HiAppEvent, crash analysis |
Photo/video picker (correct API — picker.PhotoViewPicker is deprecated)
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 9
});
const uris: string[] = result.photoUris;
File/document picker
import { picker } from '@kit.CoreFileKit';
const documentPicker = new picker.DocumentViewPicker(context);
const result = await documentPicker.select({
maxSelectNumber: 5,
fileSuffixFilters: ['.pdf', '.docx', '.xlsx'],
});
const uris: string[] = result;
const savePicker = new picker.DocumentViewPicker(context);
const saveResult = await savePicker.save({
newFileNames: ['export.pdf'],
});
const destUri: string = saveResult[0];
CoreSpeechKit — ASR audio format requirements
import { speechRecognizer } from '@kit.CoreSpeechKit';
const engine = await speechRecognizer.createEngine({
language: 'zh-CN',
online: 0
});
engine.startListening({
sessionId: `asr_${Date.now()}`,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
}
});
HTTP request example
import { http } from '@kit.NetworkKit';
async function fetchJson(url: string): Promise<string> {
const req = http.createHttp();
try {
const res = await req.request(url, {
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
connectTimeout: 5000, readTimeout: 10000
});
return res.result as string;
} finally {
req.destroy();
}
}
Network connectivity monitoring
import { connection } from '@kit.NetworkKit';
const hasNet = connection.hasDefaultNetSync();
const netCon = connection.createNetConnection();
netCon.on('netAvailable', () => { });
netCon.on('netLost', () => { });
netCon.on('netCapabilitiesChange', (info: connection.NetCapabilityInfo) => {
const isWifi = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_WIFI);
const isCellular = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR);
});
netCon.register(() => {});
netCon.unregister(() => {});
WebSocket
import { webSocket } from '@kit.NetworkKit';
const ws = webSocket.createWebSocket();
ws.on('open', () => { ws.send('hello'); });
ws.on('message', (err, data: string | ArrayBuffer) => {
console.info('received:', typeof data === 'string' ? data : 'binary');
});
ws.on('close', (err, { code, reason }) => { });
ws.on('error', (err) => { });
ws.connect('wss://example.com/ws', { header: { 'Authorization': 'Bearer xxx' } });
ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));
ws.close();
Launch app by type (startAbilityByType)
import { common } from '@kit.AbilityKit';
const context = getContext(this) as common.UIAbilityContext;
context.startAbilityByType('navigation', {
sceneType: 1,
destinationLatitude: 39.9042,
destinationLongitude: 116.4074,
destinationName: 'Beijing',
} as Record<string, Object>);
context.startAbilityByType('browser', {
uri: 'https://example.com',
} as Record<string, Object>);
Supported types: navigation, browser, email, finance, transit, etc.
Permissions
Declare in module.json5 → requestPermissions. For user-granted permissions, request at runtime via abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, [...]).
Atomic Services / Meta-Services (原子化服务 / 元服务)
Installation-free, small (≤10MB) apps launched from:
- Service cards (home-screen widgets built with
FormExtensionAbility)
- Search, scan, NFC, cross-device continuation
Configured via module.json5 with "installationFree": true. Build-time they produce .app packages containing multiple HAPs.
Distributed features
- Cross-device continuation (流转) —
continueAbility / onContinue/onNewWant lifecycle
- Distributed data object — synced state across devices
- Distributed file system — shared sandbox
- Device manager — discover trusted devices
Packaging types
| Type | Purpose |
|---|
| HAP (Harmony Ability Package) | Runnable module: entry or feature type |
| HSP (Harmony Shared Package) | In-app shared module, dynamic-linked at runtime |
| HAR (Harmony Archive) | Static library, packaged into HAP at build time |
Distribute via AppGallery Connect (华为应用市场).
Official samples: https://developer.huawei.com/consumer/cn/samples/
sys.symbol — icon glyph system
HarmonyOS Symbol is a 1500+ vector icon font with multi-layer color and 7 animation types.
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(24)
.fontColor([Color.Blue, Color.Green])
.symbolEffect(new BounceSymbolEffect(EffectScope.WHOLE), true)
Confirmed working names (SDK 6.0.1): xmark · plus · minus · checkmark · chevron_right · chevron_left · star · star_fill · bell · bell_fill · doc · video · mic · mic_fill · clock · trash · pencil · camera · person
Names that do NOT exist (common mistakes): photo · doc_richtext · sparkles · checklist · image (use doc or camera instead) · location_fill (use text label instead)
Find valid names: https://developer.huawei.com/consumer/cn/design/harmonyos-symbol
DevEco Studio 6.x — Project Setup
DevEco Studio 6.x requires additional Hvigor infrastructure files. Without them the IDE shows "工程结构及配置需要升级".
hvigorfile.ts (root + each module)
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: appTasks, plugins:[] }
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: hapTasks, plugins:[] }
hvigor/wrapper/hvigor-config.json5
{
"hvigorVersion": "5.0.0",
"dependencies": {
"@ohos/hvigor-ohos-plugin": "5.0.0"
}
}
build-profile.json5 — SDK version placement
SDK versions go in one place only — either under app OR inside each product, not both. Mixing them causes "Sync Failed".
Recommended workflow: Always let DevEco Studio generate the scaffold (新建项目 → Empty Ability), then copy your source files in.
ArkTS strict-mode compiler errors (SDK 6.0.1)
No object literals as types
parseOutput(): { text: string; tags: string[] } { ... }
interface ParsedOutput { text: string; tags: string[]; }
parseOutput(): ParsedOutput { ... }
No any / unknown
const task = JSON.parse(rawStr) as AgentTask;
navDestination requires a @Builder function reference
.navDestination((name, param) => { if (name==='X') MyPage() })
@Builder function PageRouter(name: string) { if (name==='X') MyPage() }
Navigation(stack) { ... }.navDestination(PageRouter)
@Entry build() root must be a container
Wrap in Stack(), Column(), or Row() — a custom component alone is not a container.
display.width is pixels, not vp
function isTablet(): boolean {
const dm = display.getDefaultDisplaySync();
return (dm.width / dm.densityPixels) >= 840;
}
All user_grant permissions need reason + usedScene
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:permission_media_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
INTERNET is system_grant — no extra fields needed.
Common gotchas
-
Don't mix FA and Stage models — FA is legacy; HarmonyOS NEXT only supports Stage.
-
this in ArkUI callbacks — arrow functions are required; regular function () {} loses this.
-
@State on nested objects — changes to nested props don't trigger updates; use @Observed/@ObjectLink or reassign the whole object.
-
Array item updates — replace the item (arr[i] = newItem) or use @Observed on the item class.
-
Resource references — use $r('app.string.foo'), $r('app.media.icon'), not string paths.
-
getContext(this) inside a component returns the UIAbilityContext; cast explicitly.
-
Async in build() is forbidden — load data in aboutToAppear() and store in @State.
-
Permissions must be declared AND requested at runtime for user-grant permissions.
-
ohpm is the package manager (similar to npm) — dependencies live in oh-package.json5.
-
Preview on device — DevEco Previewer doesn't fully simulate; always test on real HarmonyOS device or emulator.
-
Navigation has no hideSideBar — use .hideBackButton(true) instead.
-
promptAction.showToast() is deprecated — use getUIContext().getPromptAction().showToast(...) instead; wrap in try-catch for safety.
-
Floating FAB button blocks last list item — use Navigation.menus() for primary action buttons, or add bottom padding to List equal to FAB height + margin.
-
Named callbacks for on/off — anonymous functions can't be unregistered. Always store references:
session.on('stateChange', (state) => { ... });
const cb = (state: StateType) => { ... };
session.on('stateChange', cb);
session.off('stateChange', cb);
-
Batch state mutations — multiple @State changes trigger multiple re-renders. Accumulate in a temp variable, assign once:
this.list.push(a); this.list.push(b); this.list.push(c);
const tmp = [...this.list, a, b, c];
this.list = tmp;
-
State decorator selection priority — @State+@Prop/@Link/@ObjectLink (parent-child) > @Provide+@Consume (deep nesting) > LocalStorage (page-level) > AppStorage (global). Avoid AppStorage for frequently-changing data.
-
Unit conversion — px2vp(px) / vp2px(vp) via this.getUIContext().px2vp(value). Screen density: display.getDefaultDisplaySync().densityPixels.
-
Keep screen on — win.setWindowKeepScreenOn(true) during video playback / navigation; reset on pause.
Stability — crash types and error handling
| Type | Description |
|---|
| JS_ERROR | ArkTS/JS runtime exceptions (most common) — TypeError: Cannot read property 'x' of undefined |
| CPP_CRASH | Native C/C++ crash (SIGSEGV, SIGABRT) |
| APP_FREEZE | Main thread blocked >6s (ANR equivalent). Root causes: thread locks (57%), system resources (14%), heavy main-thread work (9%) |
| OOM | Out-of-memory kill |
Global error handler:
import { errorManager } from '@kit.AbilityKit';
const observer: errorManager.ErrorObserver = {
onUnhandledException(errMsg: string): void {
console.error('Uncaught: ' + errMsg);
},
onException?(errObject: Error): void {
console.error(errObject.name + ': ' + errObject.message);
}
};
const observerId = errorManager.on('error', observer);
Crash event subscription (HiAppEvent):
import { hiAppEvent } from '@kit.PerformanceAnalysisKit';
hiAppEvent.addWatcher({
name: "crashWatcher",
appEventFilters: [{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_CRASH]
}],
onReceive: (domain, appEventGroups) => { }
});
Background Tasks — 4 types
| Type | API | Duration | Use case |
|---|
| Transient | requestSuspendDelay | ~3 min max | Save data, upload logs |
| Continuous | startBackgroundRunning | Unlimited (needs notification) | Music, navigation, recording |
| Deferred | workScheduler.startWork | System-determined | Timed sync, cleanup |
| Agent Reminders | ReminderRequestTimer | System-managed | Alarms, timers |
Continuous task example:
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
const info: wantAgent.WantAgentInfo = {
wants: [{ bundleName: 'com.example.app', abilityName: 'MainAbility' }],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0, actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
const agent = await wantAgent.getWantAgent(info);
backgroundTaskManager.startBackgroundRunning(
this.context, backgroundTaskManager.BackgroundMode.AUDIO_RECORDING, agent
);
9 background modes: dataTransfer · audioPlayback · audioRecording · location · bluetoothInteraction · multiDeviceConnection · taskKeeping (2-in-1 only)
Deferred task frequency by user activity: Active=2h, Frequent=4h, Regular=24h, Rare=48h, Never used=prohibited.
Security coding rules (from official best practices)
- Set
exported: false for non-interactive abilities
- Validate all parameters crossing trust boundaries (Want intents, rpc.RemoteObject)
- Use parameterized queries — never string concat for SQL
- Replace HTTP with HTTPS; validate SSL certificates
- Never store personal data in clipboard
- Use
Asset Store Kit for sensitive short data (passwords, tokens)
- Avoid passing personal data through implicit intents
- Use code obfuscation for production builds
- Use precise
InputType (.USER_NAME, .Password) for system-level protection
- Never use debug signatures for production releases
Permission check + request pattern:
import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
const status = await atManager.checkAccessToken(
bundleInfo.appInfo.accessTokenId, 'ohos.permission.CAMERA');
const result = await atManager.requestPermissionsFromUser(context,
['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE']);
if (result.authResults[0] === 0) {
} else if (result.dialogShownResults?.[0]) {
} else {
atManager.requestPermissionOnSetting(context,
['ohos.permission.CAMERA']).then((statuses) => {
});
}
Data encryption levels: EL1 (device-level) → EL2 (user-level, default) → EL3 (accessible while locked) → EL4 (inaccessible when locked)
Network security config — HTTPS/certificate pinning for production apps:
Create src/main/resources/base/profile/network_config.json:
{
"network-security-config": {
"domain-config": [{
"domains": [{ "include-subdomains": true, "name": "api.example.com" }],
"trust-anchors": [{ "certificates": "/data/storage/el1/bundle/entry/resources/resfile/ca_cert.pem" }]
}]
}
}
Reference in module.json5: "metadata": [{ "name": "NetworkSecurityConfig", "resource": "$profile:network_config" }].
Testing — arkxtest framework
Package: @ohos/hypium (Mocha-style). Three sub-frameworks: JsUnit (unit), UiTest (UI automation), PerfTest (performance).
JsUnit
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from '@ohos/hypium';
export default function abilityTest() {
describe('MyTestSuite', () => {
beforeAll(() => { });
beforeEach(() => { });
afterEach(() => { });
afterAll(() => { });
it('sync_test', 0, () => {
expect(1 + 1).assertEqual(2);
});
it('async_test', 0, async (done: Function) => {
let result = await someAsyncOp();
expect(result).assertContain('expected');
done();
});
});
}
Key assertions: assertEqual(v) · assertContain(v) · assertTrue() · assertFalse() · assertNull() · assertUndefined() · assertNaN() · assertInstanceOf(type) · assertThrowError(fn) · assertDeepEquals(v) · assertClose(v, tolerance) · assertLarger(v) · assertLess(v) · not() (negation) · assertPromiseIsResolved() · assertPromiseIsRejected()
Test files in entry/src/ohosTest/ets/test/. For UI automation, see the UiTest section below.
仓颉 (Cangjie) is Huawei's new language (beta) — use ArkTS for all production apps until Cangjie is stable.
ArkCompiler — runtime details
- AOT mode — static type info generates optimized native machine code; no JIT warm-up
- LiteActor concurrency — Actor model with isolated memory per thread; communication via message passing
TaskPool vs Worker
| Feature | TaskPool | Worker |
|---|
| Thread management | Automatic (create/reuse/destroy) | Manual lifecycle |
| Max threads | Auto-scaled to physical cores | Max 64 per process |
| Task duration limit | 3 minutes (excluding async I/O) | Unlimited |
| Priority | HIGH / MEDIUM / LOW / IDLE | API 18+ only |
| Cancellation | Supported | Not supported |
| Thread reuse | Yes | No |
| Task groups | Yes | No |
| Delayed/periodic | Yes | No |
Use Worker when: tasks exceed 3 minutes, need persistent state, or strongly associated synchronous tasks.
@Concurrent rules
- Required on all TaskPool functions, only in
.ets files
- Allowed: regular functions, async functions
- Prohibited: arrow functions, class methods, anonymous functions, generator functions
- No closure variables — cannot reference outer scope; only local vars, params, and imports
- Cannot call other same-file functions (closure violation) — must import them
import { taskpool } from '@kit.ArkTS';
@Concurrent
function heavyCalc(n: number): number { return n * n; }
const result = await taskpool.execute(heavyCalc, 42);
const task = new taskpool.Task(heavyCalc, 42);
await taskpool.execute(task, taskpool.Priority.HIGH);
taskpool.executeDelayed(heavyCalc, 2000, 42);
taskpool.executePeriodically(heavyCalc, 5000, 42);
const group = new taskpool.TaskGroup();
group.addTask(heavyCalc, 10);
group.addTask(heavyCalc, 20);
group.addTask(heavyCalc, 30);
const results = await taskpool.execute(group);
Long-time tasks: async code (Promise/IO) in TaskPool has NO time limit (only CPU-bound sync code is capped at 3 minutes). HarmonyOS 6.0 officially supports long-running async tasks in TaskPool.
@Sendable — shared-heap reference passing
Objects on SharedHeap (process-level, all threads can access) — 100x faster than serialization for 1MB data.
@Sendable
class SharedData {
value: number = 0;
}
Constraints: Can only inherit from Sendable classes. Property types limited to: primitives, Sendable classes, @arkts.collections containers. Cannot add/delete properties at runtime. No computed properties. No # private (use private keyword).
Worker pattern
const worker = new worker.ThreadWorker('entry/ets/workers/myWorker.ets');
worker.postMessage({ type: 'compute', data: payload });
worker.onmessage = (e) => { };
worker.terminate();
const workerPort = worker.workerPort;
workerPort.onmessage = (e) => {
const result = processData(e.data);
workerPort.postMessage(result);
};
Both TaskPool and Worker: Cannot access AppStorage or UI libraries from worker threads. Different thread contexts prevent context object sharing.
OHPM — package manager
ohpm install @ohos/axios
ohpm install
oh-package.json5 example:
{
"name": "entry",
"dependencies": {
"@ohos/axios": "^2.0.0"
}
}
OHPM Central Repository: https://developer.huawei.com/consumer/cn/deveco-service
Popular ohpm libraries
@ohos/axios — HTTP client (ohpm install @ohos/axios, requires ohos.permission.INTERNET):
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from '@ohos/axios';
interface UserInfo { id: number; name: string }
axios.get<UserInfo, AxiosResponse<UserInfo>, null>('/user', { params: { ID: 12345 } })
.then((res: AxiosResponse<UserInfo>) => console.info(res.data.name))
.catch((err: AxiosError) => console.error(err.message));
interface NewUser { firstName: string; lastName: string }
axios.post<string, AxiosResponse<string>, NewUser>('/user',
{ firstName: 'Fred', lastName: 'Flintstone' });
const http = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'X-Custom-Header': 'value' },
});
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers['Authorization'] = 'Bearer ' + AppStorage.get<string>('token');
return config;
}, (err: AxiosError) => Promise.reject(err));
http.interceptors.response.use(
(res: AxiosResponse) => res,
(err: AxiosError) => {
if (err.response?.status === 401) { }
return Promise.reject(err);
}
);
import fs from '@ohos.file.fs';
const filePath = getContext(this).cacheDir + '/file.jpg';
axios({ url: 'https://example.com/file.jpg', method: 'get', filePath,
onDownloadProgress: (e) => {
console.info('Progress: ' + (e.loaded && e.total ? Math.ceil(e.loaded / e.total * 100) : 0) + '%');
}
});
import { FormData } from '@ohos/axios';
const form = new FormData();
form.append('file', 'internal://cache/photo.jpg');
axios.post<string, AxiosResponse<string>, FormData>('/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (e) => console.info(e.loaded + '/' + e.total),
});
const ctrl = new AbortController();
axios.get('/slow', { signal: ctrl.signal });
ctrl.abort();
responseType: 'string' | 'object' | 'array_buffer'. Mutual TLS requires API 11+.
@ohos/pulltorefresh — pull-down refresh + load-more (ohpm install @ohos/pulltorefresh, supports List/Scroll/Tabs/Grid/WaterFlow, API 12+):
import { PullToRefresh, PullToRefreshType } from '@ohos/pulltorefresh';
@Component
struct NewsPage {
@State data: string[] = ['Item 1', 'Item 2', 'Item 3'];
@State refreshing: boolean = false;
build() {
PullToRefresh({
pullToRefreshType: PullToRefreshType.LIST,
refreshing: $refreshing,
customList: () => { this.ListBuilder(); },
onRefresh: () => new Promise<string>((resolve) => {
setTimeout(() => {
this.data = ['Refreshed 1', 'Refreshed 2'];
resolve('Done');
}, 1000);
}),
onLoadMore: () => new Promise<string>((resolve) => {
setTimeout(() => {
for (let i = 0; i < 10; i++) this.data.push('New ' + this.data.length);
resolve('');
}, 1000);
}),
customLoad: null,
customRefresh: null,
})
}
@Builder ListBuilder() {
List() {
ForEach(this.data, (item: string) => {
ListItem() { Text(item).padding(16) }
})
}
}
}
@ohos/lottie — JSON animation (ohpm install @ohos/lottie, also consider @ohos/lottie-turbo for 30%+ perf):
import lottie, { AnimationItem } from '@ohos/lottie';
@Component
struct LottieDemo {
private canvasCtx = new CanvasRenderingContext2D(new RenderingContextSettings(true));
private animItem: AnimationItem | null = null;
private animName: string = 'myAnim';
aboutToDisappear(): void { lottie.destroy(); }
build() {
Canvas(this.canvasCtx).width(200).height(200)
.onReady(() => {
this.canvasCtx.imageSmoothingEnabled = true;
lottie.destroy(this.animName);
this.animItem = lottie.loadAnimation({
container: this.canvasCtx, renderer: 'canvas',
loop: true, autoplay: false, name: this.animName,
contentMode: 'Contain',
path: 'common/animation.json',
frameRate: 30,
});
this.animItem.addEventListener('DOMLoaded', () => {
this.animItem?.play();
});
})
}
}
Obfuscation: add -keep ./oh_modules/@ohos/lottie to obfuscation-rules.txt
@ohos/imageknife — image loading with cache (ohpm install @ohos/imageknife, API 18+; successor: imageknifepro):
import { ImageKnifeComponent, ImageKnifeAnimatorComponent } from '@ohos/imageknife';
import { ImageKnife } from '@ohos/imageknife';
await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024);
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: 'https://example.com/image.png',
placeholderSrc: $r('app.media.loading'),
errorholderSrc: $r('app.media.error'),
objectFit: ImageFit.Auto,
border: { radius: 50 },
onLoadListener: {
onLoadSuccess: (data) => { console.info('Loaded'); return data; },
onLoadFailed: (err) => console.error('Failed: ' + err),
},
progressListener: (progress: number) => console.info('Progress: ' + progress),
}
}).width(100).height(100)
ImageKnifeAnimatorComponent({
imageKnifeOption: { loadSrc: 'https://example.com/anim.gif' }
}).width(300).height(300)
Obfuscation: add -keep ./oh_modules/@ohos/imageknife to obfuscation-rules.txt
dayjs — date formatting (ohpm install dayjs, standard npm API):
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD HH:mm:ss');
dayjs('2024-01-01').fromNow();
dayjs().add(7, 'day').format('YYYY-MM-DD');
dayjs().diff(dayjs('2024-01-01'), 'day');
Debugging & tooling
- DevEco Studio — primary IDE, includes emulator, previewer, profiler, HiLog viewer
- hdc — Harmony Device Connector (like adb):
hdc shell, hdc file send, hdc hilog
- HiLog — logging:
hilog.info(0x0001, 'TAG', 'message %{public}s', arg)
- Instruments: SmartPerf / DevEco Profiler — CPU/GPU/memory/energy profiling
- DevEco Testing — UI automation, performance testing, monkey/stress, compatibility
Publishing
- Obtain a HarmonyOS developer account, complete real-name verification
- Generate signing certificate + profile in AppGallery Connect
- Configure signing in
build-profile.json5
- Build release HAP/APP bundle:
hvigorw assembleApp
- Upload to AppGallery Connect for review
Key references
Official sample projects (GitCode)
430+ open-source HarmonyOS samples at https://gitcode.com/HarmonyOS_Samples/<name>. Key projects by category:
Audio & Video
- AVCodecVideo — Video playback/recording via AVCodec (H.264/H.265, AudioVivid)
- AdaptiveVideo — Short video immersive & rotation playback
- AudioCast — Audio casting to external devices
- AudioToVideoSync — Audio-video synchronization
- DecodePlayControl — Surface-mode video playback control
- PlayShortVideosBasedOnVideoComponent — Short video player (progress bar, fullscreen, speed, autoplay)
- SmoothSwitchShortVideos — Smooth short video switching with preloading
- MusicHome — Adaptive music album app (one-time dev, multi-device)
- MusicCard — Music service widget via Form Kit
- NetAdaptiveVideoStream — Adaptive bitrate video streaming
- HMOS_LiveStream — Live streaming (broadcaster + viewer)
- UseAVTranscoderVideo — Video transcoding
- SwipePlayer — Swipe-to-switch video player
- VideoCast — Video casting to external devices
Camera & Image
- CustomCamera — Camera Kit: preview, flash, focus, exposure, front/back switch
- ImageGetAndSave — Image acquisition and saving
- PicturePreview — Image preview with zoom and swipe
- PixelMapImageEdit — Image editing via PixelMap encode/decode
- SmartPhotoPicker — Photo recommendation via PhotoPicker
- MultiPictureBeautification — Multi-device image beautification
- LongSnapshotPractice — Long screenshot implementation
- MultipleImage — Multi-image carousel with Swiper
- ImageToVideo — Compositing images into video
- CoreVisionKitOCR — OCR-based text auto-fill
UI Components & Layout
- CommonListFlows — Common list scenarios with List component
- ListScrollComponent — Scrollable list with lazy loading
- GridScrollComponent — Grid scroll with performance optimization
- WaterFlowScrollComponent — Waterfall flow layout
- SwiperPerformance — Swiper performance optimization
- ComponentEncapsulation — Component encapsulation patterns
- CommentReply — Comment reply with RichEditor (text, emoji, @mention)
- DialogHub — Universal dialog library
- CustomizeKeyboard — Custom keyboard implementation
- DragFramework — Drag-and-drop for images, rich text, lists
- TextExpand — Expandable/collapsible text
- PureTabs — Tab-based navigation UI
- FoldedHover — Foldable device hover mode
- CardInfoRefresh — Service widget creation, interaction & refresh (Form Kit)
- PageFlip — Page turning effects
- ResponsiveLayout — Responsive layout for multiple device types
- Immersive — Immersive full-screen UI
App Architecture & Navigation
- AppLifecycleManagement — App lifecycle state management
- AppStartUp — Startup task initialization (AppStartup)
- StageModelContext — Stage model context usage
- HMRouter — HMRouter-based page navigation
- NavigationSettings — Settings app with Navigation (small/large window)
- JumpBetweenApps — App-to-app jumping via App Linking
- CrossModuleReference — Native HAR/HSP module cross-reference
- CrossModuleResourceAccess — Cross-module resource access ($r, resourceManager)
- DynamicComponent — Dynamic component creation
- DesktopShortcut — Desktop shortcut entry via module.json5
- EmebedAbility — Embedded atomic service via FullScreenLaunchComponent
Data & Storage
- KVStore — Key-value database read/write
- DatabaseReadWrite — Relational database via C-API
- DataCache — Cold start acceleration with data caching
- BackupRestore — Data migration via backup/restore framework
- GenerateSandboxFile — Sandbox file generation
- NativeFileIO — Native-side file read/write
- NativeFileAccess — Native-side file access
- TurboTransJSON — High-performance JSON serialization (turbo_trans)
Network & Communication
- NetworkReconnection — App network reconnection
- NetBoost — Multi-network concurrency for acceleration
- RcpFileTransfer — File upload/download via Remote Communication Kit
- RemoteCommunicationPlatform — RCP network requests (forms, certificates, DNS)
- WebCrossDomain — Web cross-domain via ArkWeb interceptor & cookies
- MultiWeb — Responsive multi-Web layout
- OnlineEditorCollaboration — Online document editing with cross-device collaboration
Security & Auth
- UserAuth — Face/fingerprint auth + password vault auto-fill
- PermissionApplication — Permission request flow
- AntiPeep — Sensitive information anti-peep protection
- UniversalKeystoreCollection — HUKS key management (encrypt/decrypt, sign/verify)
- DigitalShield — Biometric authentication for transactions
- DeviceSecurityKit_sampleCode_SafetyDetectDemo_ArkTS — Device environment & URL safety detection
AI & ML
- MindSporeLiteArkTS — On-device image classification (MindSpore Lite ArkTS API)
- MindSporeLiteCpp — On-device image classification (MindSpore Lite C++ API)
- MSLiteHumanSegmentation — On-device human segmentation
- MSLiteSceneRecognition — On-device scene detection
- MSLiteStyleTransform — On-device image style transfer
- RAG_QA — RAG-based Q&A with on-device knowledge processing
Multi-Device & Wearable
- MultiDeviceCamera — Camera on phone, foldable, tablet with preview rotation
- MultiDeviceCommunication — One-time dev, multi-device IM app
- Phone_Connection — Phone-watch communication & heart rate monitoring
- SmartWatchCarControl — Watch car control app
- SmartWatchMap — Watch map app
- SmartWatchTakeTaxi — Watch taxi app
- WearableBus — Watch bus transit app
- WearableMusic — Watch music app
- APILevelAdapt — Multi-API version compatibility (ArkTS + Native)
Concurrency & Performance
- UseTaskPool — Multi-threaded tasks via TaskPool
- UseSendable — Sendable cross-thread communication & UI refresh
- MultiThreadIO — Database & file I/O with TaskPool + @Sendable
- NativeSubMainThreadCommunication — Native sub-thread to UI main-thread communication
- FunctionFlowRuntimeKit-SampleCode-ConcurrentQueue — FFRT concurrent queue
- FunctionFlowRuntimeKit-SampleCode-TaskGraph — FFRT task graph dependencies
- UtilizeHWCEfficiently — Low-power HWC composition
System & Platform
- BackTaskImplement — Background tasks for app continuity
- LiveViewLockScreen — Lock screen live view
- IntentsKitGameRevisit — Intent-based game revisit recommendations
- ContinuePublish — Cross-device content publishing (app continuation)
- HiTraceMeterPrefTag — Performance tracing with HiTraceMeter
- ObtainingDeviceID — Device identifier retrieval
- BluetoothLowEnergy — BLE device connection & communication
- NFCTag — NFC-based app launch
- QueryAppPackageInfo — App package info query
- SystemEnvVarSubscriber — System environment variable subscription
- DesktopExtensionKit-samplecode — Status bar integration via Desktop Extension Kit
Graphics & 3D
- Graphics3D — 3D engine API usage
- DocsSample_XComponent — XComponent self-rendering & AI analysis
- DocsSample_Graphics — Graphics subsystem samples
Web & Hybrid
- H5Launch — H5 cold start acceleration
- ExecutingJSWithJSVM — JSVM-API: create engines, execute JS, destroy
- DocsSample_ArkWeb — ArkWeb component samples
- SmallWindowScene — Small window (floating) scenario
Full catalog: https://gitcode.com/HarmonyOS_Samples — clone any sample with git clone https://gitcode.com/HarmonyOS_Samples/<name>.git
API 21 (SDK 6.0.1) — confirmed compile errors and fixes
These errors were verified against a real Mate 70 Pro build. All entries below caused ArkTS Compiler Error at assembleDevHqf.
DataChangeListener requires both new and deprecated method names
In API 21, any class that implements DataChangeListener must include ALL of:
onDataReloaded(): void
onDataAdd(index: number): void
onDataDelete(index: number): void
onDataChange(index: number): void
onDataMove(from: number, to: number): void
onDataAdded(index: number): void
onDataDeleted(index: number): void
onDataChanged(index: number): void
onDataMoved(from: number, to: number): void
onDatasetChange(dataOperations: DataOperation[]): void
This applies to every class including test stubs (NoopListener, etc).
notificationManager.addSlot() — pass SlotType, not NotificationSlot
await notificationManager.addSlot({ type: SlotType.SOCIAL_COMMUNICATION, desc: '...' })
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
Also: always annotate the constant explicitly to prevent type narrowing to literal type:
const SLOT_ID = notificationManager.SlotType.SOCIAL_COMMUNICATION
const SLOT_ID: notificationManager.SlotType = notificationManager.SlotType.SOCIAL_COMMUNICATION
NotificationRequest.slotType — incompatible SlotType modules
NotificationRequest.slotType is typed as @ohos.notification.SlotType (old module), while notificationManager.SlotType is from @ohos.notificationManager (new module). They are nominally different types — assigning new to old is a compile error. Just omit slotType (it is optional):
const request: notificationManager.NotificationRequest = {
id: NOTIFICATION_ID,
content: { ... }
}
NotificationSlotLevel does not exist on notificationManager in API 21 — remove any usage.
Permissions type — @ohos.bundleManager not importable
import type { Permissions } from '@ohos.bundleManager' fails with "Cannot find module". bundleManager is accessible only via @kit.AbilityKit. Workaround: declare a local subset type whose values are all valid Permissions literals:
type AppPermission = 'ohos.permission.CAMERA' | 'ohos.permission.APPROXIMATELY_LOCATION'
async function hasPermission(name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
const status = atManager.checkAccessTokenSync(bundleInfo.appInfo.accessTokenId, name)
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
requestPermissionsFromUser — void & Promise overload intersection
The function has both a callback overload (returns void) and a Promise overload (returns Promise<PermissionRequestResult>). TypeScript sometimes intersects them to void & Promise<…>, causing .authResults to not exist. Additionally, abilityAccessCtrl.PermissionRequestResult is not exported from the namespace in API 21.
Fix: declare the result interface locally and cast:
interface PermResult {
authResults: abilityAccessCtrl.GrantStatus[]
}
async function requestPermission(ctx: common.UIAbilityContext, name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const result = await (atManager.requestPermissionsFromUser(ctx, [name]) as Promise<PermResult>)
return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
getContext(this) is deprecated
Replace in all component methods:
const ctx = getContext(this) as common.UIAbilityContext
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext
Module-level (non-component) functions must receive ctx as a parameter instead.
Multi-device / foldable screen adaptation (API 21)
Breakpoint detection
import { display } from '@kit.ArkUI'
export const BP_SM = 'sm'
export const BP_MD = 'md'
export const BP_LG = 'lg'
export function getBreakpoint(): string {
try {
const d = display.getDefaultDisplaySync()
const widthVp = d.width / d.densityPixels
if (widthVp < 600) return BP_SM
if (widthVp < 840) return BP_MD
return BP_LG
} catch (e) {
return BP_SM
}
}
Foldable screen — listen for fold/unfold events
private displayListener: (id: number) => void = (_id: number) => {
this.breakpoint = getBreakpoint()
}
aboutToAppear(): void {
this.breakpoint = getBreakpoint()
display.on('change', this.displayListener)
}
aboutToDisappear(): void {
display.off('change', this.displayListener)
}
Responsive layout switching (if/else, not .visibility)
Always use if/else to switch between phone and tablet layouts. .visibility(Visibility.None) hides but still lays out — wastes resources and can cause measurement bugs.
if (this.breakpoint === BP_SM) {
List({ space: 8 }) { LazyForEach(...) }
} else {
Scroll() {
GridRow({
columns: { sm: 1, md: 2, lg: 3 },
gutter: { x: 12, y: 12 },
breakpoints: { value: ['600vp', '840vp'] }
}) {
ForEach(this.gridItems, (item) => { GridCol() { MyCard({ record: item }) } })
}
}
}
GridRow/GridCol does not support LazyForEach
GridRow/GridCol is a responsive layout container, not a lazy loader. Use ForEach inside it. To keep the grid reactive to data changes, maintain a @State gridItems: T[] that is updated via a DataChangeListener:
class GridRefreshListener implements DataChangeListener {
private updateFn: () => void
constructor(fn: () => void) { this.updateFn = fn }
onDataReloaded(): void { this.updateFn() }
onDataAdd(_i: number): void { this.updateFn() }
onDataAdded(_i: number): void { this.updateFn() }
onDataChange(_i: number): void { }
onDataChanged(_i: number): void { }
}
@State gridItems: MyRecord[] = []
private gridListener = new GridRefreshListener(() => {
this.gridItems = [...this.dataSource.getAllData()]
})
aboutToAppear() { this.dataSource.registerDataChangeListener(this.gridListener) }
aboutToDisappear() { this.dataSource.unregisterDataChangeListener(this.gridListener) }
Share breakpoint via @Provide/@Consume
Declare in the @Entry root, consume in any NavDestination child:
@Provide('breakpoint') breakpoint: string = BP_SM
@Consume('breakpoint') breakpoint: string
@Provide/@Consume works across Navigation/NavDestination because they are in the same component subtree.
Location Kit (geoLocationManager)
import { geoLocationManager } from '@kit.LocationKit'
const pos = await geoLocationManager.getCurrentLocation({
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
timeoutMs: 10000
})
const addresses = await geoLocationManager.getAddressesFromLocation({
latitude: pos.latitude,
longitude: pos.longitude,
maxItems: 1
})
Weather Service Kit — weather data API
Requires ohos.permission.LOCATION (or APPROXIMATELY_LOCATION) if using device location.
import { weatherService } from '@kit.WeatherServiceKit';
const weatherRequest: weatherService.WeatherRequest = {
location: { latitude: 39.9042, longitude: 116.4074 },
limitedDatasets: [
weatherService.Dataset.CURRENT,
weatherService.Dataset.DAILY,
weatherService.Dataset.HOURLY,
weatherService.Dataset.ALERTS,
weatherService.Dataset.INDICES,
weatherService.Dataset.TIDES,
weatherService.Dataset.MINUTE,
],
};
try {
const weather = await weatherService.getWeather(weatherRequest);
} catch (err) {
console.error('Weather fetch failed:', err);
}
Notification Kit (notificationManager)
import { notificationManager } from '@kit.NotificationKit'
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
const enabled = await notificationManager.isNotificationEnabled()
if (!enabled) await notificationManager.requestEnableNotification()
const request: notificationManager.NotificationRequest = {
id: 1001,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '标题', text: '正文', additionalText: '副文本' }
},
deliveryTime: Date.now(),
showDeliveryTime: true,
tapDismissed: true
}
await notificationManager.publish(request)
await notificationManager.cancel(1001)
relationalStore (SQLite)
import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
const store = await relationalStore.getRdbStore(ctx, {
name: 'my_db.db',
securityLevel: relationalStore.SecurityLevel.S1
})
await store.executeSql('CREATE TABLE IF NOT EXISTS records (...)')
const values: relationalStore.ValuesBucket = { id: '1', title: 'Hello' }
await store.insert('records', values)
const predicates = new relationalStore.RdbPredicates('records')
predicates.orderByDesc('timestamp')
const rs = await store.query(predicates, ['id', 'title', 'timestamp'])
while (rs.goToNextRow()) {
const id = rs.getString(rs.getColumnIndex('id'))
}
rs.close()
const predicates2 = new relationalStore.RdbPredicates('records')
predicates2.equalTo('id', '1')
await store.update({ title: 'Updated' }, predicates2)
const predicates3 = new relationalStore.RdbPredicates('records')
predicates3.equalTo('id', '1')
await store.delete(predicates3)
preferences (key-value settings)
import { preferences } from '@kit.ArkData'
const store = await preferences.getPreferences(ctx, 'user_settings')
const val = (await store.get('sort_order', 'time_desc')) as string
await store.put('sort_order', 'time_asc')
await store.flush()
UiTest — common patterns (arkxtest)
import { Driver, ON, Component } from '@ohos.UiTest'
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'
const DELEGATOR = AbilityDelegatorRegistry.getAbilityDelegator()
beforeAll(async (done: Function) => {
await DELEGATOR.startAbility({ bundleName: 'com.example.app', abilityName: 'EntryAbility' })
await new Promise(r => setTimeout(r, 2000))
done()
})
it('example', 0, async (done: Function) => {
const driver = Driver.create()
const btn = await driver.findComponent(ON.text('发布'))
const input = await driver.findComponent(ON.type('TextInput'))
const items = await driver.findComponents(ON.type('SymbolGlyph'))
await btn.click()
await input.inputText('hello')
await new Promise(r => setTimeout(r, 800))
const btnAfter = await driver.findComponent(ON.text('发布'))
const bounds = await btn.getBounds()
await driver.pressBack()
done()
})
Key rules:
- Every UiTest API call inside
it() must be awaited
- After any click/input that changes state, component references may be stale — always re-
findComponent
- Tests only run on real device or emulator — Previewer does not support UiTest
Driver.create() fresh per it block (don't share across tests)
- Use
sleep / setTimeout after navigation to let the new page render before asserting
ArkWeb — Web component
Embed web content in ArkTS via the Web component from @kit.ArkWeb.
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct BrowserPage {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptAccess(true)
.domStorageAccess(true)
.fileAccess(true)
.onPageBegin((event) => { console.log('loading:', event?.url); })
.onPageEnd((event) => { console.log('loaded:', event?.url); })
.onErrorReceive((event) => { console.error('web error:', event?.error.getErrorInfo()); })
.darkMode(WebDarkMode.Auto)
.forceDarkAccess(true)
}
}
}
JS ↔ ArkTS bridge (javaScriptProxy)
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptProxy({
object: {
callNative: (msg: string) => {
console.log('from JS:', msg);
return 'ArkTS received: ' + msg;
}
},
name: 'NativeBridge',
methodList: ['callNative'],
controller: this.controller
})
this.controller.runJavaScript('window.updateUI("data")', (err, result) => {
console.log('JS result:', result);
});
Custom User-Agent and cookies
webview.WebviewController.setCustomUserAgent(
webview.WebviewController.getDefaultUserAgent() + ' MyApp/1.0'
);
import { webCookie } from '@kit.ArkWeb';
webCookie.setCookie('https://example.com', 'token=abc; path=/');
webCookie.saveCookieAsync();
Intercept resource requests
Web({ src: '...', controller: this.controller })
.onInterceptRequest((event) => {
if (event?.request.getRequestUrl().includes('/api/')) {
const resp = new WebResourceResponse();
resp.setResponseData('{"intercepted":true}');
resp.setResponseMimeType('application/json');
resp.setResponseEncoding('utf-8');
resp.setResponseCode(200);
resp.setReasonMessage('OK');
return resp;
}
return null;
})
Form Kit — ArkTS service cards (服务卡片)
Service cards run in a sandboxed FormExtensionAbility process; they use a subset of ArkUI.
FormExtensionAbility lifecycle
import { FormExtensionAbility, formBindingData, FormInfo, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
const formData = { title: 'Hello', count: 0 };
return formBindingData.createFormBindingData(formData);
}
onCastToNormalForm(formId: string) { }
onUpdateForm(formId: string) {
const data = formBindingData.createFormBindingData({ count: Date.now() });
formProvider.updateForm(formId, data);
}
onRemoveForm(formId: string) { }
onFormEvent(formId: string, message: string) {
console.log('card event:', formId, message);
}
}
Card UI (EntryFormAbility/pages/Card.ets)
@Entry
@Component
struct CardPage {
@LocalStorageProp('title') title: string = '';
@LocalStorageProp('count') count: number = 0;
build() {
Column({ space: 8 }) {
Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
Text(`Count: ${this.count}`).fontSize(14)
Button('+1')
.onClick(() => {
postCardAction(this, {
action: 'message',
params: { event: 'increment' }
});
})
}.padding(12)
}
}
module.json5 — declare the form
{
"extensionAbilities": [{
"name": "EntryFormAbility",
"srcEntry": "./ets/formextensionability/EntryFormAbility.ets",
"type": "form",
"metadata": [{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}]
}]
}
resources/base/profile/form_config.json
{
"forms": [{
"name": "widget",
"displayName": "$string:widget_display_name",
"description": "$string:widget_desc",
"src": "./ets/formextensionability/pages/Card.ets",
"uiSyntax": "arkts",
"window": { "designWidth": 720, "autoDesignWidth": true },
"colorMode": "auto",
"isDynamic": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": ["1*2", "2*2", "2*4"]
}]
}
UIDesignKit — icon processing & HdsNavigation
Available from HarmonyOS 5.0+ (API 12+). Provides Huawei Design System components.
hdsDrawable — icon adaptive processing
import { hdsDrawable } from '@kit.UIDesignKit';
const drawable = new hdsDrawable.HdsAdaptiveIconDrawable(
context,
iconResource,
{ size: 48 }
);
const pixelMap = await drawable.getPixelMap();
HdsNavigation — system-style navigation component
import { HdsNavigation, HdsNavigationItem } from '@kit.UIDesignKit';
@Entry
@Component
struct MainPage {
@State currentIndex: number = 0;
private tabs: HdsNavigationItem[] = [
{ icon: $r('app.media.home'), selectedIcon: $r('app.media.home_filled'), label: '首页' },
{ icon: $r('app.media.mine'), selectedIcon: $r('app.media.mine_filled'), label: '我的' },
];
build() {
Column() {
Blank()
HdsNavigation({
items: this.tabs,
selectedIndex: this.currentIndex,
onItemClick: (index: number) => { this.currentIndex = index; }
})
}.height('100%')
}
}
HdsNavigation supports: dynamic blur background, custom content areas (badges, dot indicators), message count badges on items.
Map Kit — MapComponent
import { mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
@Entry
@Component
struct MapPage {
private mapOptions: mapCommon.MapOptions = {
position: {
target: { latitude: 39.9042, longitude: 116.4074 },
zoom: 12
}
};
private mapController?: map.MapComponentController;
build() {
Column() {
MapComponent({
mapOptions: this.mapOptions,
mapCallback: (err, controller) => {
if (!err) {
this.mapController = controller;
this.addMarker();
}
}
}).width('100%').layoutWeight(1)
}
}
private addMarker() {
this.mapController?.addMarker({
position: { latitude: 39.9042, longitude: 116.4074 },
title: 'Tiananmen',
snippet: 'Beijing city center'
});
}
}
Required permissions in module.json5: ohos.permission.LOCATION and ohos.permission.APPROXIMATELY_LOCATION.
Cold start optimization
HarmonyOS measures cold start as: app launch → first frame rendered. Target: < 1000ms on mid-range device.
Lazy-import (import())
import { HeavyModule } from '../utils/HeavyModule';
let heavyModule: typeof import('../utils/HeavyModule') | null = null;
async function useHeavy() {
if (!heavyModule) {
heavyModule = await import('../utils/HeavyModule');
}
heavyModule.HeavyModule.doWork();
}
Network requests — defer until after first frame
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err) => {
if (!err) {
AppStartupData.prefetch();
}
});
}
Other cold start rules
- Avoid heavy synchronous work in
onCreate() / onWindowStageCreate() — use TaskPool for >10ms tasks
- Minimize global singleton construction at module load time
- Use
@Reusable on list item components to avoid remeasure/relayout on first display
- Avoid
hilog calls inside tight rendering loops (I/O cost)
- Profile with DevEco Profiler → Launch task to see exact frame timeline
Memory optimization
onMemoryLevel callback
onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) {
ImageCache.instance.clear();
DataCache.instance.trimToSize(10);
} else if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_LOW) {
DataCache.instance.trimToSize(50);
}
}
LRUCache — bounded image / data cache
import { util } from '@kit.ArkTS';
class ImageCache {
static instance = new ImageCache();
private lru = new util.LRUCache<string, PixelMap>(50);
put(key: string, pm: PixelMap) { this.lru.put(key, pm); }
get(key: string): PixelMap | undefined { return this.lru.get(key); }
clear() { this.lru.clear(); }
trimToSize(n: number) {
while (this.lru.length > n) {
this.lru.afterRemoval(false, this.lru.keys()[0], undefined, undefined);
}
}
}
Purgeable memory (large bitmaps)
import { image } from '@kit.ImageKit';
const pixelMap = await image.createPixelMap(buffer, {
size: { width: 1920, height: 1080 },
editable: false
});
General memory rules
- Unregister listeners in
aboutToDisappear() / onBackground() to prevent leaks
- Avoid capturing
this in long-lived closures (keeps component alive)
- Reuse PixelMap objects instead of recreating them for repeated renders
- Use
image.ImageSource + lazy decode for thumbnails — don't decode full resolution
- TaskPool threads share no heap —
@Sendable objects avoid copy but transfer ownership
Camera Kit
CameraPicker — no-permission photo/video capture
Simplest way to capture a photo or video — launches the system camera UI. No camera permission required.
import { camera, cameraPicker as picker } from '@kit.CameraKit';
import { fileIo, fileUri } from '@kit.CoreFileKit';
const pathDir = getContext(this).filesDir;
const filePath = pathDir + `/${Date.now()}.tmp`;
fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
const pickerProfile: picker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveUri: fileUri.getUriFromPath(filePath)
};
const result = await picker.pick(
getContext(this),
[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO],
pickerProfile
);
if (result.resultCode === 0) {
const uri = result.resultUri;
const isPhoto = result.mediaType === picker.PickerMediaType.PHOTO;
}
Full camera session (preview + photo capture)
Requires ohos.permission.CAMERA. Flow: CameraManager → CameraInput → Session → PreviewOutput/PhotoOutput.
import { camera } from '@kit.CameraKit';
const cameraManager = camera.getCameraManager(context);
const cameras = cameraManager.getSupportedCameras();
const cameraDevice = cameras[0];
const cameraInput = cameraManager.createCameraInput(cameraDevice);
await cameraInput.open();
const capability = cameraManager.getSupportedOutputCapability(
cameraDevice, camera.SceneMode.NORMAL_PHOTO
);
const previewOutput = cameraManager.createPreviewOutput(
capability.previewProfiles[0], surfaceId
);
const photoOutput = cameraManager.createPhotoOutput(capability.photoProfiles[0]);
const session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(previewOutput);
session.addOutput(photoOutput);
await session.commitConfig();
await session.start();
photoOutput.on('photoAvailable', (err, photo) => {
const imageObj = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, (err, component) => {
const buffer: ArrayBuffer = component.byteBuffer!;
imageObj.release();
});
});
photoOutput.capture({ quality: camera.QualityLevel.QUALITY_LEVEL_HIGH });
XComponent for camera preview
@Entry @Component
struct CameraPreview {
private ctrl = new XComponentController();
private surfaceId = '';
build() {
XComponent({ type: XComponentType.SURFACE, controller: this.ctrl })
.onLoad(() => {
this.surfaceId = this.ctrl.getXComponentSurfaceId();
})
.width('100%').height('100%')
}
}
Key permissions: ohos.permission.CAMERA (photo/video), ohos.permission.MICROPHONE (audio recording).
Audio Kit — playback & recording
AudioRenderer (ArkTS) — PCM playback
import { audio } from '@kit.AudioKit';
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 0
}
};
const renderer = await audio.createAudioRenderer(rendererOptions);
renderer.on('writeData', (buffer: ArrayBuffer) => {
return audio.AudioDataCallbackResult.VALID;
});
await renderer.start();
Audio focus (InterruptEvent)
System manages audio focus automatically based on StreamUsage. Listen for focus changes:
renderer.on('audioInterrupt', (event: audio.InterruptEvent) => {
if (event.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
switch (event.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
break;
}
} else if (event.hintType === audio.InterruptHint.INTERRUPT_HINT_RESUME) {
await renderer.start();
}
});
StreamUsage → volume type mapping
| StreamUsage | Volume type | Typical use |
|---|
MUSIC / MOVIE / AUDIOBOOK / GAME | Media | Music, video, audiobook, game BGM |
VOICE_COMMUNICATION | Voice call | VoIP calls |
RINGTONE / NOTIFICATION | Ringtone | Incoming call, notifications |
ALARM | Alarm | Alarms (plays on speaker even with BT) |
NAVIGATION | — | Nav voice (ducks music, doesn't pause) |
AudioSession — custom focus strategy
const audioManager = audio.getAudioManager();
const sessionManager = audioManager.getSessionManager();
const strategy: audio.AudioSessionStrategy = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
await sessionManager.activateAudioSession(strategy);
sessionManager.on('audioSessionDeactivated', (event) => {
console.info('Session deactivated:', event.reason);
});
await sessionManager.deactivateAudioSession();
Concurrency modes: CONCURRENCY_DEFAULT, CONCURRENCY_MIX_WITH_OTHERS, CONCURRENCY_DUCK_OTHERS, CONCURRENCY_PAUSE_OTHERS.
Background playback requirements
Apps playing audio in background must:
- For media/game streams (
MUSIC/MOVIE/AUDIOBOOK/GAME): integrate AVSession AND request long-running task (AUDIO_PLAYBACK)
- For other audible streams: request
AUDIO_PLAYBACK long-running task only
- Without these, system will mute and freeze the app when backgrounded
Code obfuscation (ArkGuard)
Enable in build-profile.json5 → arkOptions.obfuscation. ArkGuard supports name obfuscation, code compression, and comment removal for ArkTS/TS/JS.
Key obfuscation options
# obfuscation-rules.txt
-enable-property-obfuscation # obfuscate property names
-enable-toplevel-obfuscation # obfuscate top-level scope names
-enable-export-obfuscation # obfuscate import/export names
-enable-filename-obfuscation # obfuscate file/folder names
-enable-string-property-obfuscation # also obfuscate string literal property names
-compact # remove whitespace and newlines
-remove-log # delete console.* statements
-print-namecache ./nameCache.json # save name mapping (keep for crash analysis!)
Common whitelist scenarios (-keep-property-name)
These properties must be whitelisted to avoid runtime errors:
-keep-property-name
# 1. Dynamic property access
# obj['x' + i] → keep x0, x1, x2 ...
# Object.defineProperty(obj, 'y', {}) → keep y
# 2. JSON parsing fields
# JSON.parse / JSON.stringify → keep all serialized field names
# 3. Network request fields
# http.request extraData: { username, password } → keep field names
# 4. Database fields (ValuesBucket keys)
# relationalStore column names → keep
# 5. NAPI / .so interop
# import from 'libentry.so' → keep exported function names
# 6. ArkUI @Component @State properties are auto-preserved (no action needed)
# SDK API names are auto-preserved (no action needed)
Whitelist for file names (-keep-file-name)
-keep-file-name
# System-loaded files that must not be renamed:
# - Worker thread files
# - Ability entry files (auto-collected since DevEco 5.0.3.500)
# - Dynamic import paths: const m = await import('./SomePath')
# - System route table paths (pageSourceFile in route_map.json, auto since API 20)
Preserve specific names in source (@KeepSymbol)
class ImportantClass {
criticalProp: string = '';
}
Gotchas
- ArkGuard uses global property whitelisting — if you keep
name, ALL properties named name across all files are kept
enum members are auto-collected into whitelist when building HAR with property obfuscation
- Always save
nameCache.json per release — needed to decode obfuscated crash stacks
-compact removes all newlines → crash stack line numbers become useless (column info not provided in release builds)
- String properties matching SDK API constants (e.g.,
'ohos.want.action.home') are NOT auto-whitelisted — add them manually if used as property keys
Scan Kit — barcode scanning & generation
Default UI scan (no camera permission needed)
Launches the system scan UI. Camera is pre-authorized — no permission request required.
import { scanCore, scanBarcode } from '@kit.ScanKit';
const options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
};
try {
const result = await scanBarcode.startScanForResult(
getContext(this),
options
);
console.info('Scan result:', result.originalValue);
} catch (err) {
console.error('Scan failed:', err.code, err.message);
}
Supported code types: QR Code, Data Matrix, PDF417, Aztec, EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code 39/93/128, ITF-14.
Image decode (detect barcode in photo)
import { scanCore, scanBarcode, detectBarcode } from '@kit.ScanKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const picker = new photoAccessHelper.PhotoViewPicker();
const pickerResult = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
const inputImage: detectBarcode.InputImage = { uri: pickerResult.photoUris[0] };
const results = await detectBarcode.decode(inputImage, {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true
});
Custom UI scan (requires ohos.permission.CAMERA)
Use customScan from @kit.ScanKit for full control over the scan UI:
import { scanCore, scanBarcode, customScan } from '@kit.ScanKit';
customScan.init({ scanTypes: [scanCore.ScanType.ALL], enableMultiMode: true });
const viewControl: customScan.ViewControl = { width, height, surfaceId };
const results = await customScan.start(viewControl);
await customScan.release();
Barcode generation
import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
const pixelMap: image.PixelMap = await generateBarcode.createBarcode('https://example.com', {
scanType: scanCore.ScanType.QR_CODE,
height: 400,
width: 400
});
Supports generating: QR Code, EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code 39/93/128, ITF-14, Data Matrix, PDF417, Aztec.
Account Kit — Huawei ID login
Configure Client ID in module.json5
{
"module": {
"name": "entry",
"type": "entry",
"metadata": [{
"name": "client_id",
"value": "YOUR_CLIENT_ID" // from AGC console
}]
}
}
One-click login (enterprise developers, non-game apps)
Retrieves phone number + UnionID in a single tap. Requires manual signing + AGC permission approval.
import { authentication } from '@kit.AccountKit';
import { util } from '@kit.ArkTS';
const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
authRequest.scopes = ['quickLoginAnonymousPhone'];
authRequest.state = util.generateRandomUUID();
authRequest.forceAuthorization = false;
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(authRequest);
const anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone as string;
import { loginComponentManager, LoginWithHuaweiIDButton } from '@kit.AccountKit';
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.QUICK_LOGIN,
supportDarkMode: true
},
controller: this.controller
})
Obfuscation whitelist — if property obfuscation is enabled, add to obfuscation-rules.txt:
-keep-property-name
quickLoginAnonymousPhone
Huawei Account login (all developers)
Retrieves UnionID/OpenID via LoginWithHuaweiIDButton or API. Supports enterprise + individual developers.
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.ID,
supportDarkMode: true
},
controller: this.controller
})
import { authentication } from '@kit.AccountKit';
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = true;
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController(getContext(this));
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
Silent login
No user interaction — retrieves UnionID for returning users (reinstall, device switch).
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = false;
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
Common error codes
| Code | Meaning | Action |
|---|
1001502001 | Huawei Account not logged in | Show other login methods |
1001502005 | Network error | Retry or show other methods |
1001502012 | User cancelled | No action needed |
1001500001 | Certificate fingerprint check failed | Check signing config |
1001502014 | Missing scopes/permissions | Check AGC permission approval |
1005300001 | User did not agree to protocol | Show agreement dialog |
Key concepts
| ID type | Scope | Use case |
|---|
| OpenID | Per-app unique | Identify user within one app |
| UnionID | Per-developer unique | Identify user across multiple apps by same developer |
| GroupUnionID | Per-account-group unique | Identify user across affiliated developers |
For cross-platform user data continuity, prefer UnionID over OpenID.
App continuation (应用接续) — cross-device migration
Enable cross-device task handoff: migrate UIAbility state from device A to device B.
Enable continuation in module.json5
{
"module": {
"abilities": [{
"name": "EntryAbility",
"continuable": true // enable cross-device migration
}]
}
}
Source device — save migration data
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
wantParam['currentPage'] = 'DetailPage';
wantParam['articleId'] = this.articleId;
const targetVersion = wantParam['version'] as number;
if (targetVersion < 2) {
return AbilityConstant.OnContinueResult.MISMATCH;
}
return AbilityConstant.OnContinueResult.AGREE;
}
Target device — restore data
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.articleId = want.parameters?.['articleId'] as number;
this.context.restoreWindowStage(this.storage);
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.articleId = want.parameters?.['articleId'] as number;
this.context.restoreWindowStage(this.storage);
}
}
Dynamic migration control
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE);
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE);
Cross-device migration with different Ability names
Use continueType in module.json5 to link different Abilities across devices:
// Device A
{ "name": "PhoneAbility", "continueType": ["myApp_main"] }
// Device B
{ "name": "TabletAbility", "continueType": ["myApp_main"] }
Prerequisites
- Both devices logged into same Huawei Account
- Wi-Fi + Bluetooth enabled (or "Multi-device Collaboration Enhanced" enabled)
- "Settings → Multi-device Collaboration → Continuation" enabled
- App installed on both devices
Push Kit — push notifications
Get Push Token
Call in onCreate() of your UIAbility. Token identifies device+app for push messages.
import { pushService } from '@kit.PushKit';
pushService.getToken().then((token: string) => {
console.info('Push token:', token);
}).catch((err: BusinessError) => {
console.error('Failed to get push token:', err.code, err.message);
});
Prerequisites: Enable Push Service in AGC console first, otherwise getToken() returns error 1000900010.
Token changes on: app reinstall, factory reset, deleteToken() + re-getToken(). Always call getToken() on each app launch to keep server-side token fresh.
Receive push messages (module.json5)
Configure a UIAbility with action.ohos.push.listener to receive token updates and push data:
"skills": [{ "actions": ["action.ohos.push.listener"] }]
Push Kit supports: notification messages, voice broadcast, card refresh, background messages, live view, in-app call messages.
AVSession Kit — media playback control (required for background audio)
Critical: All apps playing audio/video in background must create an AVSession. Without it, the system will force-pause your audio when the app goes to background.
Create and activate session
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
const session = await AVSessionManager.createAVSession(context, 'MyPlayer', 'audio');
await session.setAVMetadata({
assetId: 'song_001',
title: 'Song Title',
artist: 'Artist Name',
mediaImage: 'https://example.com/cover.jpg',
duration: 240000
});
await session.setAVPlaybackState({
state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY,
position: { elapsedTime: 0, updateTime: Date.now() },
speed: 1.0
});
session.on('play', () => { });
session.on('pause', () => { });
session.on('playNext', () => { });
session.on('playPrevious', () => { });
session.on('seek', (position: number) => { });
await session.activate();
Background playback requirements
For media streams (MUSIC/MOVIE/AUDIOBOOK/GAME):
- Create AVSession (as above)
- Request
AUDIO_PLAYBACK long-running task via Background Tasks Kit
- Both are mandatory — missing either one causes background audio to be silenced
Unsupported commands
Use session.off() to unregister commands your app doesn't support. The system playback center will gray out corresponding buttons.
session.off('playPrevious');
session.off('toggleFavorite');
Resource access — $r() and $rawfile()
Resource directory structure
resources/
├─ base/ # default resources (always matched)
│ ├─ element/ # string.json, color.json, float.json, etc.
│ ├─ media/ # images, audio, video
│ └─ profile/ # custom JSON config files
├─ zh_CN/element/ # Chinese locale override
├─ en_US/element/ # English locale override
├─ dark/element/ # dark mode override
├─ rawfile/ # raw files (not compiled, accessed by path)
└─ resfile/ # installed to sandbox, read-only access
Qualifier order: MCC_MNC → language_script_region → orientation → device → colorMode → density.
Accessing resources
Text($r('app.string.hello_world'))
.fontSize($r('app.float.text_size_body'))
.fontColor($r('app.color.primary'))
Image($r('app.media.app_icon'))
Text($r('app.string.greeting', 'Alice', 5))
Text($r('app.plural.item_count', 2, 2))
Image($rawfile('images/banner.png'))
Text('Hello')
.fontColor($r('sys.color.ohos_id_color_emphasize'))
.fontSize($r('sys.float.ohos_id_text_size_headline1'))
Text($r('[library].string.shared_text'))
Programmatic access via ResourceManager
const resMgr = getContext(this).resourceManager;
const str = resMgr.getStringByNameSync('hello_world');
const rawFd = resMgr.getRawFd('data.json');
Application file paths (Context properties)
| Context property | Path | Purpose |
|---|
filesDir | base/files/ | Persistent app data (survives app updates) |
cacheDir | base/cache/ | Cache (system may auto-clean when space low) |
tempDir | base/temp/ | Temp files (cleaned on app exit) |
databaseDir | database/ | Database files (relationalStore, etc.) |
preferencesDir | base/preferences/ | Preferences KV store |
bundleCodeDir | bundle/ | Installed HAP resources (read-only) |
distributedFilesDir | distributedfiles/ | Cross-device shared files |
Payment Kit — Huawei Pay (physical goods & services)
For physical goods/services only (hotels, rides, bills). Virtual goods use IAP Kit instead.
import { paymentService } from '@kit.PaymentKit';
import { common } from '@kit.AbilityKit';
const orderStr = '{"app_id":"...","merc_no":"...","prepay_id":"...","timestamp":"...","noncestr":"...","sign":"..."}';
const context = getContext(this) as common.UIAbilityContext;
paymentService.requestPayment(context, orderStr)
.then(() => { console.info('Payment succeeded'); })
.catch((err: BusinessError) => { console.error('Payment failed:', err.code, err.message); });
Server flow: your server calls /api/v2/aggr/preorder/create/app → gets prepayId → builds signed orderStr → returns to client.
Supports: Phone, Tablet, PC/2in1. China mainland only.
Core Vision Kit — OCR, face detection, subject segmentation
On-device AI capabilities from @kit.CoreVisionKit. China mainland only, no simulator support.
Text recognition (OCR)
import { textRecognition } from '@kit.CoreVisionKit';
await textRecognition.init();
const visionInfo: textRecognition.VisionInfo = { pixelMap: myPixelMap };
const result = await textRecognition.recognizeText(visionInfo, {
isDirectionDetectionSupported: false
});
console.info('OCR result:', result.value);
await textRecognition.release();
Supports: Chinese (simplified/traditional), English, Japanese, Korean. Input: JPEG/PNG, 720p+ recommended.
Face detection
import { faceDetector } from '@kit.CoreVisionKit';
await faceDetector.init();
const faces: faceDetector.Face[] = await faceDetector.detect({ pixelMap: myPixelMap });
await faceDetector.release();
Subject segmentation
import { subjectSegmentation } from '@kit.CoreVisionKit';
await subjectSegmentation.init();
const result = await subjectSegmentation.doSegmentation(
{ pixelMap: myPixelMap },
{ maxCount: 5, enableSubjectDetails: true, enableSubjectForegroundImage: true }
);
await subjectSegmentation.release();
fileIo — application file read/write
Core file operations via @kit.CoreFileKit. All paths should come from Context properties (filesDir, cacheDir, etc.).
import { fileIo as fs } from '@kit.CoreFileKit';
const context = getContext(this);
const filePath = context.filesDir + '/data.json';
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(file.fd, JSON.stringify({ key: 'value' }));
fs.closeSync(file);
const readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const readLen = fs.readSync(readFile.fd, buf);
const content = String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)));
fs.closeSync(readFile);
const exists = fs.accessSync(filePath);
const entries = fs.listFileSync(context.filesDir);
fs.copyFileSync(filePath, context.cacheDir + '/data_backup.json');
fs.unlinkSync(filePath);
const stat = fs.statSync(filePath);
console.info(`size: ${stat.size}, mtime: ${stat.mtime}`);
Read file from Picker URI
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const len = fs.readSync(file.fd, buf);
fs.closeSync(file);
AVPlayer — unified audio/video playback
AVPlayer from @kit.MediaKit handles mp4/mp3/mkv/mpeg-ts etc. — just provide the source, no manual decode needed.
import { media } from '@kit.MediaKit';
const avPlayer = await media.createAVPlayer();
avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'initialized':
avPlayer.prepare();
break;
case 'prepared':
avPlayer.play();
break;
case 'completed':
avPlayer.release();
break;
}
});
avPlayer.on('error', (err) => {
console.error('AVPlayer error:', err.message);
avPlayer.release();
});
const file = fs.openSync(context.filesDir + '/video.mp4', fs.OpenMode.READ_ONLY);
avPlayer.fdSrc = { fd: file.fd, offset: 0, length: fs.statSync(file.fd).size };
avPlayer.url = 'https://example.com/audio.mp3';
AVPlayer with video surface (XComponent)
avPlayer.on('stateChange', (state: string) => {
if (state === 'initialized') {
avPlayer.surfaceId = xComponentSurfaceId;
avPlayer.prepare();
} else if (state === 'prepared') {
avPlayer.play();
}
});
avPlayer.url = 'https://example.com/video.mp4';
AVPlayer controls
avPlayer.pause();
avPlayer.play();
avPlayer.seek(30000);
avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X);
avPlayer.setVolume(0.5);
avPlayer.stop();
avPlayer.release();
AVRecorder — audio/video recording
import { media } from '@kit.MediaKit';
const recorder = await media.createAVRecorder();
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: {
audioBitrate: 48000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 2000000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 1920,
videoFrameHeight: 1080,
videoFrameRate: 30
},
url: `fd://${file.fd}`,
rotation: 0
};
await recorder.prepare(config);
await recorder.start();
await recorder.stop();
await recorder.release();
For background playback: must create AVSession + request AUDIO_PLAYBACK long-running task (see AVSession Kit section).
Image Kit — decode, transform, encode
Decode image to PixelMap
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
const imageSource = image.createImageSource(context.filesDir + '/photo.jpg');
const pixelMap = await imageSource.createPixelMap({
editable: true,
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
});
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const imageSource2 = image.createImageSource(file.fd);
const imageSource3 = image.createImageSource(arrayBuffer);
const rawFd = context.resourceManager.getRawFd('image.png');
const imageSource4 = image.createImageSource(rawFd);
const info = await pixelMap.getImageInfo();
console.info(`${info.size.width} x ${info.size.height}`);
PixelMap transforms
pixelMap.crop({ x: 0, y: 0, size: { width: 400, height: 400 } });
pixelMap.scale(0.5, 0.5);
pixelMap.rotate(90);
pixelMap.flip(true, false);
pixelMap.flip(false, true);
pixelMap.opacity(0.5);
pixelMap.translate(100, 100);
Encode PixelMap to file
import { image } from '@kit.ImageKit';
const packer = image.createImagePacker();
const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 90 };
const data: ArrayBuffer = await packer.packing(pixelMap, packOpts);
packer.release();
Release resources
pixelMap.release();
imageSource.release();
App Linking — deep links & app-to-app navigation
Configure deep link in module.json5
{
"module": {
"abilities": [{
"name": "EntryAbility",
"skills": [{
"entities": ["entity.system.home", "entity.system.browsable"],
"actions": ["ohos.want.action.home", "ohos.want.action.viewData"],
"uris": [{ "scheme": "https", "host": "example.com", "path": "/detail" }],
"domainVerify": true // enable App Linking verification
}]
}]
}
}
Handle incoming link — cold start (onCreate)
import { url } from '@kit.ArkTS';
export default class EntryAbility extends UIAbility {
private targetPage: string = '';
private linkParams: Record<string, string> = {};
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
}
private parseUri(want: Want): void {
if (want?.uri) {
const urlObj = url.URL.parseURL(want.uri);
this.linkParams = Object.fromEntries(urlObj.params.entries());
this.targetPage = urlObj.pathname;
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
const page = this.targetPage === '/detail' ? 'pages/Detail' : 'pages/Index';
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
}
windowStage.loadContent(page);
}
}
Handle link — app already running (onNewWant)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
AppStorage.setOrCreate('newWantFlag', true);
}
}
Page listens for newWantFlag via @StorageLink + @Watch and navigates accordingly.
Share Kit — cross-app content sharing
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor } from '@kit.ArkData';
const shareData = new systemShare.SharedData({
utd: uniformTypeDescriptor.UniformDataType.HYPERLINK,
content: 'https://example.com/article/123',
title: 'Article Title',
description: 'Article preview text',
});
const controller = new systemShare.ShareController(shareData);
controller.show(this.context, {
previewMode: systemShare.SharePreviewMode.DEFAULT,
selectionMode: systemShare.SelectionMode.SINGLE,
});
Supported UTD types: HYPERLINK, PLAIN_TEXT, HTML, IMAGE (pass uri from file), FILE.
Custom dialog — openCustomDialog & openBindSheet
openCustomDialog (general-purpose modal/non-modal)
import { ComponentContent } from '@kit.ArkUI';
@Builder
function dialogContentBuilder(params: { message: string; close: () => void }) {
Column({ space: 16 }) {
Text(params.message).fontSize(16)
Button('OK').onClick(() => params.close())
}
.padding(24)
.backgroundColor(Color.White)
.borderRadius(16)
}
const uiContext = this.getUIContext();
const contentNode = new ComponentContent(uiContext, wrapBuilder(dialogContentBuilder), {
message: 'Hello',
close: () => uiContext.getPromptAction().closeCustomDialog(contentNode),
});
uiContext.getPromptAction().openCustomDialog(contentNode, {
alignment: DialogAlignment.Center,
isModal: true,
autoCancel: true,
});
openBindSheet (bottom half-modal sheet)
const uiContext = this.getUIContext();
const sheetNode = new ComponentContent(uiContext, wrapBuilder(sheetBuilder), params);
uiContext.openBindSheet(sheetNode, {
title: { title: 'Select Option' },
height: SheetSize.MEDIUM,
preferType: SheetType.BOTTOM,
detents: [SheetSize.MEDIUM, SheetSize.LARGE, 200],
backgroundColor: '#F1F3F5',
}, targetComponentId);
bindContentCover (full-screen modal overlay)
@State isPresented: boolean = false;
@Builder
fullScreenContent() {
Column() {
Text('Full Screen Modal').fontSize(20)
Button('Close').onClick(() => { this.isPresented = false; })
}.width('100%').height('100%').backgroundColor(Color.White)
}
Button('Show').onClick(() => { this.isPresented = true; })
.bindContentCover($$this.isPresented, this.fullScreenContent(), {
modalTransition: ModalTransition.DEFAULT,
})
Note: Do NOT use the deprecated CustomDialog or @ohos.promptAction — use UIContext.getPromptAction().openCustomDialog(), UIContext.openBindSheet(), and .bindContentCover() instead.
Keyboard layout adaptation (软键盘适配)
Set keyboard avoidance mode (in UIAbility)
import { KeyboardAvoidMode } from '@kit.ArkUI';
windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
Prevent a component from moving with keyboard
Row() { }
.expandSafeArea([SafeAreaType.KEYBOARD])
.zIndex(1)
Monitor keyboard height
import { window } from '@kit.ArkUI';
window.getLastWindow(this.getUIContext().getHostContext()).then(win => {
win.on('keyboardHeightChange', (height: number) => {
this.keyboardHeight = this.getUIContext().px2vp(height);
});
});
Focus control
TextInput().defaultFocus(true)
this.getUIContext().getFocusController().requestFocus('inputId');
this.getUIContext().getFocusController().clearFocus();
Dark mode adaptation (深色模式适配)
Resource qualifier approach (recommended)
Place light-mode colors/images in resources/base/, dark-mode variants (same filenames) in resources/dark/:
resources/base/element/color.json → { "color": [{ "name": "bg_color", "value": "#FFFFFF" }] }
resources/dark/element/color.json → { "color": [{ "name": "bg_color", "value": "#1A1A1A" }] }
resources/base/media/icon.png → light icon
resources/dark/media/icon.png → dark icon
Usage: $r('app.color.bg_color') / $r('app.media.icon') — auto-switches with system theme.
Detect & react to color mode changes
onCreate(): void {
AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode);
}
onConfigurationUpdate(newConfig: Configuration): void {
AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
}
@StorageProp('currentColorMode') @Watch('onColorModeChange')
currentColorMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
onColorModeChange(): void {
const isDark = this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
}
Programmatic mode switching
this.getUIContext().getHostContext()?.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
Background upload & download (request.agent)
import { request } from '@kit.BasicServicesKit';
Background upload (supports pause/resume)
const config: request.agent.Config = {
action: request.agent.Action.UPLOAD,
url: 'https://example.com/upload',
mode: request.agent.Mode.BACKGROUND,
method: 'POST',
data: formItems,
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { });
task.on('completed', (progress) => { });
await task.start();
await task.pause();
await task.resume();
Background download (auto breakpoint resume)
const config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: 'https://example.com/file.zip',
mode: request.agent.Mode.BACKGROUND,
saveas: `./downloads/file.zip`,
overwrite: true,
gauge: true,
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { });
await task.start();
Desktop shortcuts (桌面快捷方式)
1. Create resources/base/profile/shortcuts_config.json
{
"shortcuts": [
{
"shortcutId": "id_scan",
"label": "$string:shortcut_scan",
"icon": "$media:ic_scan",
"wants": [{
"bundleName": "com.example.myapp",
"moduleName": "entry",
"abilityName": "EntryAbility",
"parameters": { "shortcutKey": "ScanPage" }
}]
}
]
}
2. Register in module.json5
"abilities": [{
"name": "EntryAbility",
"metadata": [{
"name": "ohos.ability.shortcuts",
"resource": "$profile:shortcuts_config"
}]
}]
3. Route based on shortcut parameter
const shortcutKey = want?.parameters?.shortcutKey as string;
if (shortcutKey === 'ScanPage') {
AppStorage.setOrCreate('targetPage', 'pages/Scan');
}
Page reads @StorageProp('targetPage') and navigates accordingly.
Cross-module resource access (跨模块资源访问)
Access HAR resources (same as local)
Text($r('app.string.string_in_har'))
Image($r('app.media.image_in_har'))
this.context.resourceManager.getStringSync($r('app.string.string_in_har').id);
Access HSP resources (prefix with module name)
Text($r('[hsp1].string.string_in_hsp'))
Image($r('[hsp1].media.image_in_hsp'))
Or via createModuleContext:
import { common } from '@kit.AbilityKit';
const hspContext = await common.application.createModuleContext(this.context, 'hsp1');
const str = hspContext.resourceManager.getStringByNameSync('string_in_hsp');
Custom font (自定义字体)
const uiContext = windowStage.getMainWindowSync().getUIContext();
uiContext.getFont().registerFont({
familyName: 'MyCustomFont',
familySrc: $rawfile('MyCustomFont.ttf'),
});
Text('Hello').fontFamily('MyCustomFont')
Font size follow/ignore system setting — configure in profile/configuration.json:
{ "configuration": { "fontSizeScale": "followSystem", "fontSizeMaxScale": "2" } }
Reference in app.json5: "configuration": "$profile:configuration".
Screen orientation (横竖屏切换)
import { window } from '@kit.ArkUI';
const win = await window.getLastWindow(this.context);
win.setPreferredOrientation(window.Orientation.USER_ROTATION_LANDSCAPE);
win.setPreferredOrientation(window.Orientation.USER_ROTATION_PORTRAIT);
win.setPreferredOrientation(window.Orientation.AUTO_ROTATION);
win.on('windowSizeChange', (size) => {
const orientation = display.getDefaultDisplaySync().orientation;
this.isLandscape = (orientation === display.Orientation.LANDSCAPE ||
orientation === display.Orientation.LANDSCAPE_INVERTED);
});
Also configurable in module.json5: "abilities": [{ "orientation": "portrait" }].
Options: portrait, landscape, auto_rotation, auto_rotation_landscape, follow_desktop.
Clipboard — pasteboard read/write
import { pasteboard } from '@kit.BasicServicesKit';
const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, 'Hello World');
const board = pasteboard.getSystemPasteboard();
await board.setData(pasteData);
const data = await board.getData();
if (data.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
const text = data.getPrimaryText();
}
Supported MIME types: MIMETYPE_TEXT_PLAIN, MIMETYPE_TEXT_HTML, MIMETYPE_TEXT_URI, MIMETYPE_PIXELMAP.
Gesture conflict resolution (手势冲突处理)
hitTestBehavior — control touch event response
Stack() {
BottomComponent().hitTestBehavior(HitTestMode.None)
TopComponent().hitTestBehavior(HitTestMode.Transparent)
}
| Mode | Behavior |
|---|
Default | Self responds, blocks siblings |
Transparent | Self responds, does NOT block siblings |
None | Skips self, passes to siblings |
Block | Only self responds, stops all propagation |
Gesture binding priority
ParentComponent()
.priorityGesture(TapGesture().onAction(() => { }))
ParentComponent()
.parallelGesture(PanGesture().onAction(() => { }))
ParentComponent()
.gesture(TapGesture(), GestureMask.IgnoreInternal)
GestureGroup modes
GestureGroup(GestureMode.Sequence, LongPressGesture(), PanGesture())
GestureGroup(GestureMode.Parallel, PinchGesture(), RotationGesture())
GestureGroup(GestureMode.Exclusive, TapGesture(), SwipeGesture())
Note: System gestures (onClick, onTouch, drag, bindMenu) always win over custom gestures of the same type.
Immersive window (沉浸式/全屏/避让区)
Extend component into status bar & navigation bar
Column() { }
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
const win = windowStage.getMainWindowSync();
win.setWindowLayoutFullScreen(true);
Get safe area dimensions for manual padding
import { window } from '@kit.ArkUI';
const win = await window.getLastWindow(context);
const systemAvoid = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
const topHeight = px2vp(systemAvoid.topRect.height);
const bottomHeight = px2vp(systemAvoid.bottomRect.height);
win.on('avoidAreaChange', (options: window.AvoidAreaOptions) => {
if (options.type === window.AvoidAreaType.TYPE_SYSTEM) {
}
});
Hide/show system bars
win.setSpecificSystemBarEnabled('status', false);
win.setSpecificSystemBarEnabled('navigationIndicator', false);
Status bar text color (light/dark content)
win.setWindowSystemBarProperties({
statusBarContentColor: '#FFFFFF',
});
Common list operations (列表常用操作)
Swipe-to-delete (left swipe action)
ListItem() { }
.swipeAction({
end: {
builder: () => {
Button('Delete').backgroundColor(Color.Red)
.onClick(() => {
animateTo({ duration: 300 }, () => {
this.dataList.splice(index, 1);
});
})
},
actionAreaDistance: 56,
},
edgeEffect: SwipeEdgeEffect.Spring,
})
Drag reorder
List() {
ForEach(this.dataList, (item: string, index: number) => {
ListItem() { Text(item) }
})
}
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
this.dragIndex = itemIndex;
})
.onItemDragMove((event: ItemDragInfo, itemIndex: number, insertIndex: number) => {
animateTo({ duration: 200 }, () => {
const tmp = this.dataList.splice(this.dragIndex, 1);
this.dataList.splice(insertIndex, 0, tmp[0]);
this.dragIndex = insertIndex;
});
})
Pull-down refresh
Refresh({ refreshing: $$this.isRefreshing }) {
List() { }
}
.onRefreshing(() => {
this.isRefreshing = false;
})
Scroll to bottom (chat-style)
const scroller = new Scroller();
List({ scroller }) { }
scroller.scrollEdge(Edge.Bottom);
scroller.scrollToIndex(this.messages.length - 1);
Keep scroll position on data insert (LazyForEach)
List() { LazyForEach(this.dataSource, ...) }
.maintainVisibleContentPosition(true)
ListItemGroup + sticky header (grouped list)
@Builder sectionHeader(title: string) {
Text(title).fontSize(14).fontColor('#99000000')
.width('100%').padding({ left: 16, top: 8, bottom: 8 })
.backgroundColor('#F1F3F5')
}
List() {
ForEach(this.groups, (group: GroupData) => {
ListItemGroup({ header: this.sectionHeader(group.title) }) {
ForEach(group.items, (item: ItemData) => {
ListItem() { Text(item.name) }
})
}
})
}
.sticky(StickyStyle.Header)
onReachEnd — load more data
List() { LazyForEach(this.dataSource, ...) }
.onReachEnd(() => {
if (!this.isLoading) {
this.isLoading = true;
this.loadNextPage().then(() => { this.isLoading = false; });
}
})