Building PromptTuner: HarmonyOS 6 Prompt Assistant App — Architecture, ArkUI Patterns & Performance Lessons
This technical case study details the end-to-end development of PromptTuner, a HarmonyOS 6 native app for prompt discovery, optimization, and management, covering three-layer architecture, ArkUI declarative UI patterns, state management, API abstraction, local analytics, privacy compliance, and measurable performance gains.
Project Background & Motivation
As large language models become ubiquitous, writing effective prompts has emerged as a new skill barrier. Users struggle to craft clear, structured prompts and lack tools to persist and reuse high-quality prompts as reusable "creative assets." PromptTuner was built to address this gap — a HarmonyOS-native application focused on prompt discovery, optimization, management, and learning.
Product Positioning & Core Features
PromptTuner targets lightweight creators, operations staff, students, customer service/sales, indie developers, and AI beginners. It delivers five core modules:
Prompt Square : Categorized/tagged browsing, search, like, favorite, report, one-click copy; multi-dimensional filtering by popularity, recency, category.
Prompt Optimization & Diagnosis : Input raw prompt; supports clarification, style enhancement, disambiguation; provides multi-dimensional scoring (completeness, clarity, effectiveness, professionalism) with improvement suggestions via AI.
Prompt Templates : Slot-based parameterized generation; local saving of personal templates for quick customized prompt creation.
Learning Center : Tutorial articles, practical tips; favorites and jump-to-feature links; systematic knowledge base from beginner to advanced.
My Profile : Unified management of favorites, history, personal templates, learning content; all data stored locally for privacy.
Technology Stack & Architecture
Stack : HarmonyOS 6 SDK (phone-first, extensible to tablet/PC), ArkTS + ArkUI declarative UI, internal @xbl component library (TabBar, XPrivacyDialog, common buttons), hvigor build system with multi-module structure (entry + shared config).
Three-Layer Architecture :
UI Layer : ArkTS/ArkUI multi-page app — Square, Tools, Learning, Profile pages; each manages own state; router + params for inter-page communication.
Service Layer : Public UI capabilities via @xbl library; network access encapsulated in PromptApi.ets, ArticleApi.ets, CategoryApi.ets with unified doPost, response format compatibility, error handling, 60s timeout; local data & analytics via AnalyticsUtil.ets + StorageUtil.ets (Preferences-based).
Data Layer : DataModels.ets defines Prompt, Template, Article models; rawfile seed data + remote APIs form complete data system.
Key Technical Practices & HarmonyOS 6 Feature Adoption
1. ArkUI Declarative UI & Multi-Tab Skeleton
Problem : Imperative UI led to complex cross-page state sync and verbose navigation code.
Solution : @Entry + @Component for pages; MainPage as root container with TabBar + TabItem[] configuring four tabs (Square, Tools, Learning, Profile) using system SymbolGlyph icons (e.g., sys.symbol.square_grid_2x2). Index.ets shows XPrivacyDialog on first launch via ArkUI overlay + custom callbacks.
Key Code — MainPage tab structure:
@Entry
@Component
struct MainPage {
@State selectedTab: string = 'square'
@State optimizePromptParam: string = ''
@State optimizeModeParam: string = ''
private tabs: TabItem[] = [
{ key: 'square', title: '广场', icon: $r('sys.symbol.square_grid_2x2'), selectedIcon: $r('sys.symbol.square_fill_grid_2x2') },
{ key: 'create', title: '工具', icon: $r('sys.symbol.wand_and_stars'), selectedIcon: $r('sys.symbol.wand_and_stars_fill') },
{ key: 'learning', title: '学习', icon: $r('sys.symbol.book_pages'), selectedIcon: $r('sys.symbol.book_pages_fill') },
{ key: 'profile', title: '我的', icon: $r('sys.symbol.person'), selectedIcon: $r('sys.symbol.person_fill') }
]
build() {
Column() {
Stack() { this.renderContent() }.layoutWeight(1)
TabBar({ tabs: this.tabs, selectedTab: this.selectedTab, selectedColor: $r('app.color.color_primary'), onTabChange: (key: string) => { this.selectedTab = key; console.info('切换到标签:', key) } })
}.width('100%').height('100%').backgroundColor($r('app.color.surface_background'))
}
@Builder renderContent() {
if (this.selectedTab === 'square') { SquareListPage() }
else if (this.selectedTab === 'create') { CreateAndOptimizePage({ optimizePrompt: this.optimizePromptParam, optimizeMode: this.optimizeModeParam }) }
else if (this.selectedTab === 'learning') { LearningCenterPage() }
else if (this.selectedTab === 'profile') { ProfilePage() }
}
}Index Privacy Dialog : In aboutToAppear, checks preferences store 'aiprompt_privacy_store' for 'privacy_agreed' flag; if false, shows XPrivacyDialog; on agree, writes true to preferences and navigates to MainPage via router.replaceUrl.
2. State Management & Cross-Page Parameter Passing
Problem : Tab switches and back-navigation lost parameters; optimization tool reuse was low.
Solution : @State manages selectedTab, optimizePromptParam, optimizeModeParam. RouterHelper.getParams() in aboutToAppear and onPageShow parses route params for directed tab jumps and parameter backfill (e.g., from draft list or diagnosis result). CreateAndOptimizePage merges "create + optimize" into one reusable container.
Key Code — Parameter handling in MainPage:
aboutToAppear(): void {
const params: ESObject | null = RouterHelper.getParams(this.getUIContext()) as ESObject | null
if (params) {
if (params.selectedTab) this.selectedTab = params.selectedTab as string
if (params.optimizePrompt) this.optimizePromptParam = params.optimizePrompt as string
if (params.optimizeMode) this.optimizeModeParam = params.optimizeMode as string
}
}
onPageShow(): void {
const params: ESObject | null = RouterHelper.getParams(this.getUIContext()) as ESObject | null
if (params) {
if (params.selectedTab) this.selectedTab = params.selectedTab as string
if (params.optimizePrompt) this.optimizePromptParam = params.optimizePrompt as string
if (params.optimizeMode) this.optimizeModeParam = params.optimizeMode as string
}
}Result : One-tap jump from Square/Learning to Tools tab with prompt and mode pre-filled, cutting user steps significantly.
3. API Abstraction & Prompt Optimization Flow
Problem : Backend responses unstable — sometimes {code, data}, sometimes raw data — causing scattered parsing logic across pages.
Solution : PromptApi.ets centralizes generatePrompt, diagnosePrompt, optimizePrompt, likePrompt. Response format detection via property checks (hasCode, hasData, hasMessage) normalizes to single format; errors thrown uniformly. Diagnosis dimensions (completeness, clarity, effectiveness, professionalism) converted from object to array for UI rendering (score bars, radar charts).
Key Code — Response compatibility handling:
static async generatePrompt(params: GeneratePromptParams): Promise<GeneratePromptResponse> {
try {
const response: ESObject = await doPost<ESObject>({ host: ApiConstants.HOST_URL, url: `${ApiConstants.API_PREFIX}/generate`, data: { requirement: params.requirement }, timeout: 60000 })
let responseData: ESObject = response
const hasCode = (responseData as ESObject).code !== undefined
const hasData = (responseData as ESObject).data !== undefined
const hasMessage = (responseData as ESObject).message !== undefined
if (hasCode && hasData && hasMessage) {
const apiResponse = responseData as ApiResponse<GeneratePromptResponse>
if (apiResponse.code !== 0 || !apiResponse.data) throw new Error(apiResponse.message || '生成失败')
return apiResponse.data
}
const hasPrompt = (responseData as ESObject).prompt !== undefined
const hasTitle = (responseData as ESObject).title !== undefined
if (hasPrompt && hasTitle) return responseData as GeneratePromptResponse
throw new Error('响应格式不正确:缺少必要字段')
} catch (error) { console.error('generatePrompt error:', JSON.stringify(error)); throw error }
}Diagnosis dimension conversion :
const dimensions: DiagnoseDimension[] = [
{ name: 'completeness', score: data.dimensions.completeness, maxScore: 25, label: '完整性' },
{ name: 'clarity', score: data.dimensions.clarity, maxScore: 25, label: '清晰度' },
{ name: 'effectiveness', score: data.dimensions.effectiveness, maxScore: 25, label: '有效性' },
{ name: 'professionalism', score: data.dimensions.professionalism, maxScore: 25, label: '专业性' }
]Result : Page layer focuses on business/UI; duplicate error handling and data transformation eliminated; future API changes require single-point modification.
4. Local Analytics & User Behavior Analysis
Problem : Need lightweight MVP analytics without heavy SDK.
Solution : AnalyticsUtil.ets defines EventType, EventRecord, AnalyticsData; uses Preferences for local event storage. eventCache + MAX_CACHE_SIZE (batch write threshold) reduces frequent I/O; MAX_EVENTS caps total stored events. High-level wrappers: trackPageView, trackSearch, trackCopy, trackFavorite, trackLike, trackOptimize, trackTemplateGenerate, trackReport.
Key Code — Batched flush:
static async flushEvents(): Promise<void> {
if (!AnalyticsUtil.dataPreferences || AnalyticsUtil.eventCache.length === 0) return
try {
const existingEvents = await AnalyticsUtil.getEvents()
let index = 0; const cacheLength = AnalyticsUtil.eventCache.length
while (index < cacheLength) { existingEvents.push(AnalyticsUtil.eventCache[index]); index++ }
let finalEvents = existingEvents
if (existingEvents.length > AnalyticsUtil.MAX_EVENTS) {
const startIndex = existingEvents.length - AnalyticsUtil.MAX_EVENTS
finalEvents = []; let i = startIndex
while (i < existingEvents.length) { finalEvents.push(existingEvents[i]); i++ }
}
await AnalyticsUtil.dataPreferences.put('events', JSON.stringify(finalEvents))
await AnalyticsUtil.dataPreferences.flush()
AnalyticsUtil.eventCache = []
console.info(`Flushed ${cacheLength} events to storage`)
} catch (error) { console.error('Failed to flush events:', JSON.stringify(error)) }
}Result : Full offline/weak-network behavior capture; exportable for post-launch analysis; 80% I/O reduction under high-frequency operations.
5. Privacy Dialog & Fine-Grained Permission Management
Problem : First-time users sensitive to privacy terms; clunky flow causes drop-off.
Solution : XPrivacyDialog for consistent system-styled dialog. Index.ets reads privacy_agreed via preferences.getPreferences; fallback to show dialog on read failure. "View Agreement/Policy" uses getUrlWithDarkMode for dark-mode-adapted web links, router.pushUrl to CommonWebPage.
Key Code — Dialog callbacks:
XPrivacyDialog({
appName: this.appName,
detailMode: XPrivacyDetailMode.CUSTOM,
onOpenDetail: (type: XPrivacyOpenType) => {
if (type === XPrivacyOpenType.USER) { const url = getUrlWithDarkMode(getContext(this) as common.UIAbilityContext, PrivacyConstants.USER_AGREEMENT_URL); router.pushUrl({ url: RouteConstants.COMMON_WEB, params: { url, title: '用户协议' } }) }
else { const url = getUrlWithDarkMode(getContext(this) as common.UIAbilityContext, PrivacyConstants.PRIVACY_POLICY_URL); router.pushUrl({ url: RouteConstants.COMMON_WEB, params: { url, title: '隐私政策' } }) }
},
onAgree: async () => { const store = await preferences.getPreferences(getContext(this) as common.UIAbilityContext, 'aiprompt_privacy_store'); await store.put('privacy_agreed', true); await store.flush(); this.showPrivacyDialog = false; this.hasAgreedPrivacy = true; this.navigateToMain() },
onDecline: () => { console.info('用户拒绝隐私协议') }
})Result : Higher privacy acceptance; clearer user data awareness; foundation for future permission scopes (clipboard, network).
6. Design Tokens & Dark Mode Adaptation
Problem : Visual inconsistency across pages/components (colors, spacing, radii).
Solution : DesignTokens.ets + resource files centralize Spacing, Radius, color resources; referenced via $r('app.color.*') with dark-mode support. Common components (CommonCard, CommonButton, CommonTag) consume tokens for consistent look.
Key Code — Token definitions & usage:
export class Spacing { static readonly XS: number = 4; static readonly SM: number = 8; static readonly MD: number = 16; static readonly LG: number = 24; static readonly XL: number = 32; static readonly XXL: number = 48; }
export class Radius { static readonly SM: number = 8; static readonly MD: number = 12; static readonly LG: number = 16; static readonly XL: number = 20; }
// CommonCard.ets
build() { Column() { if (this.content) this.content() }.width('100%').padding(Spacing.MD).backgroundColor($r('app.color.card_background')).borderRadius(Radius.MD).onClick(() => { if (this.onCardClick) this.onCardClick() }) }Result : Semantic layer between design and implementation; global theme changes via token updates only.
7. Performance Optimization (Lists & Network)
Scenarios : Square/search large card lists; Learning Center long lists + rich text.
Tactics : Pagination + lazy loading; unified 60s timeout + error handling at API layer; analytics decoupled via cache-batch flush.
Result : 60fps scrolling on mid/low-end devices; headroom for future AI features.
Measurable Outcomes
Development Efficiency
Component library (ArkUI + @xbl) cut core scaffolding (skeleton, tabs, privacy dialog) by ~40% vs. from-scratch.
Unified API wrappers (PromptApi, ArticleApi) and AnalyticsUtil pushed code reuse to 70%+; new features focus on business logic.
Three-layer architecture clarified responsibilities; centralized error handling & data transformation in API layer reduced maintenance cost ~50%.
Performance & UX
First-screen load ≤1.5s (target met) via lazy loading, pagination, local cache; async privacy check doesn't block startup.
Tab switch response <100ms (state-driven).
Square→Tools jump reduced from 5 steps to 2 via parameter passing.
Core actions (copy, favorite, optimize) within 2–3 steps.
Analytics batch flush cut I/O frequency 80%; no main-thread impact under load.
List scrolling holds 60fps on mid/low-end devices.
Lessons Learned & Retrospective
HarmonyOS 6 Developer Experience Shifts
Strong typing : ArkTS interfaces (PromptApi, DataModels) caught ~60% of potential bugs at compile time; new member onboarding ~30% faster.
Declarative UI : aboutToAppear/onPageShow lifecycle hooks enforce unidirectional data flow; ~40% less boilerplate vs. imperative UI.
Declarative UI Best Practices
@State/@Builder combo for tab content — clear, testable, avoids manual DOM manipulation.
Common components (CommonButton, CommonCard, CommonTag) hit 80%+ reuse; unified style, reduced drift.
Keep build() pure UI description; side effects (data fetch, init) in lifecycle or utils — cleaner pages, easier testing, lower maintenance.
Cross-Device Extensibility
Current phone-first; architecture decouples UI from Service/Data layers, enabling tablet/PC multi-column layouts and distributed soft-bus sync (phone collect → tablet deep edit, multi-window side-by-side).
Ecosystem Wishlist
System-level AI tool integrations (clipboard, notes, browser) for workflow embedding.
More open advanced capabilities within permission model.
More community component libraries (like @xbl) to lower tooling barrier.
Richer technical sharing to grow developer ecosystem.
Future Roadmap
Feature iteration : Semantic search (intent understanding, similarity retrieval), multi-language, multi-level optimization; template collaboration (multi-level categories, team sharing, RBAC).
HarmonyOS tracking : New ArkUI components, large-screen/wearable/PC adaptive layouts, multi-window, distributed task flow via soft bus.
Ecosystem integration : Cross-app with learning, creation, office, notes, IME apps (e.g., one-tap optimize note/browser content into prompt); system clipboard, quick apps, voice assistant; cloud LLM services, enterprise knowledge bases, open platforms.
Closing Note
PromptTuner bridges users and AI via HarmonyOS-native experience — a one-stop space to discover, use, learn, and manage prompts, turning disposable prompts into persistent creative assets. Technology serves creation; the team continues iterating with better tech and UX, and welcomes community collaboration to advance the HarmonyOS ecosystem.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
