Mobile Development 29 min read

Building a Children's Flag Learning App with HarmonyOS: Core Speech Kit & ArkTS Deep Dive

A HarmonyOS developer shares the technical journey of building 'Shengqu Guoqitong', a children's flag learning app, detailing why HarmonyOS was chosen over Android, Flutter, and React Native, and how Core Speech Kit's offline TTS/ASR and ArkTS declarative UI enabled efficient development of voice-driven, interactive educational features.

51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
Building a Children's Flag Learning App with HarmonyOS: Core Speech Kit & ArkTS Deep Dive

Project Demo

The article showcases a HarmonyOS app called "Shengqu Guoqitong" (声趣国旗通) designed for children aged 5-8 to learn national flags through voice interaction, drawing, and puzzle games.

Introduction: From Pain Points to Solution

Existing flag-learning apps rely on static text and images, creating high cognitive barriers for young children with limited literacy. The author chose HarmonyOS to build an app centered on three principles: cartoonish, ultra-simple, and highly interactive. The development leveraged HarmonyOS Core Speech Kit and ArkTS declarative UI.

Technology Selection: Why HarmonyOS?

Horizontal Comparison: Mainstream Solutions Analysis

The author spent a week comparing four mobile development options across speech capability, development efficiency, performance, ecosystem maturity, and overall score:

HarmonyOS : Speech ⭐⭐⭐⭐⭐ (native, offline), Development ⭐⭐⭐⭐⭐ (ArkTS declarative UI), Performance ⭐⭐⭐⭐⭐ (native), Ecosystem ⭐⭐⭐⭐ (rapidly growing), Score 95

Android (Kotlin) : Speech ⭐⭐⭐ (requires third-party SDK), Development ⭐⭐⭐⭐ (Jetpack Compose), Performance ⭐⭐⭐⭐⭐ (mature), Ecosystem ⭐⭐⭐⭐⭐ (complete), Score 85

Flutter : Speech ⭐⭐ (plugin-dependent), Development ⭐⭐⭐⭐⭐ (hot reload), Performance ⭐⭐⭐⭐ (near-native), Ecosystem ⭐⭐⭐⭐ (active), Score 75

React Native : Speech ⭐⭐ (third-party plugins), Development ⭐⭐⭐ (moderate learning curve), Performance ⭐⭐⭐ (JS bridge overhead), Ecosystem ⭐⭐⭐⭐ (mature), Score 65

HarmonyOS was selected for five core reasons detailed below.

1. Core Speech Kit: The Killer Feature for Children's Education

Why Speech Capability Matters

For 5-8 year olds, voice interaction is not just convenient but necessary to lower cognitive barriers:

Limited literacy : Children know 500-1500 characters; complex text UIs hinder experience.

Active verbal expression : Kids prefer speaking over typing; voice matches cognitive habits.

Immersive learning : Multi-sensory audio-visual stimulation boosts memory retention by 60%+ (educational psychology research).

HarmonyOS Core Speech Kit Technical Advantages

① True Offline Capability, Zero Network Dependency

Traditional third-party SDKs (iFlytek, Baidu) require cloud API calls, 200-500ms latency, per-call costs (~¥0.002), and upload privacy data. Core Speech Kit runs fully offline, <50ms latency, zero cost, no call limits, and processes data locally complying with Children's Personal Information Protection Regulations. Real-world test: all voice features work in subway/airplane mode.

② Mandarin Recognition Accuracy 95%+

A blogger's experiment with 10 children (6-8 years) speaking 20 country names showed:

HarmonyOS ASR: 95.5% accuracy, ✅ specifically optimized for child voice, ✅ good dialect tolerance

iFlytek: 92.3%, ✅ supported, ⭐ excellent dialect tolerance

Baidu Speech: 89.7%, ⚠️ average child adaptation, ✅ good dialect tolerance

Google Speech: 76.2%, ❌ not adapted for Chinese children, ❌ poor dialect tolerance

③ Flexible Pause Control and Speed Adjustment

Core Speech Kit supports SSML markup for precise rhythm control:

// Smart pauses give children time to understand
const text = `
中国的首都是北京[p500]
货币是人民币[p500]
语言是普通话[p1000]
你记住了吗?
`;
TextToSpeechManager.getInstance().speak(text);

Traditional TTS often reads in one breath, leaving children unable to keep up.

④ Multi-Voice Support for Affinity

Six voices available (standard male, standard female, child voice, etc.). The author chose standard female (person: 0) + interactive broadcast style (style: 'interaction-broadcast'), which tested highest acceptance among children.

2. ArkTS + ArkUI: Triple Development Efficiency

Revolutionary Declarative UI Experience

The author implemented the same continent card (rounded corners, shadow, gradient background, click animation) in three frameworks:

HarmonyOS ArkTS: 15 lines, single file

// HarmonyOS ArkTS: 15 lines
Column() {
  Text('亚洲')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
  Text('48个国家')
    .fontSize(14)
    .fontColor('#666')
}
.width('100%')
.padding(16)
.backgroundColor('#FF6B6B')
.borderRadius(16)
.shadow({ radius: 8, color: '#1A000000', offsetY: 2 })
.onClick(() => this.navigateToContinentList('亚洲'))

Android XML + Kotlin: 35 lines, needs XML + Kotlin + resource files

<!-- Android XML + Kotlin: 35 lines -->
<!-- activity_card.xml -->
<androidx.cardview.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="16dp"
    app:cardElevation="8dp">

    <LinearLayout
        android:orientation="vertical"
        android:padding="16dp"
        android:background="@drawable/gradient_bg">

        <TextView
            android:text="亚洲"
            android:textSize="18sp"
            android:textStyle="bold"/>

        <TextView
            android:text="48个国家"
            android:textSize="14sp"
            android:textColor="#666666"/>
    </LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Also need gradient_bg.xml, click listeners, etc. -->

Code Quantity Comparison:

HarmonyOS ArkTS : 15 lines, one file

Android Traditional : 35 lines, XML + Kotlin + resources

Flutter : 25 lines, relatively clean but deep nesting

Elegant State Management

ArkTS provides @State, @Link, @StorageLink decorators for reactive state management:

// Volume setting auto-syncs to all pages
@StorageLink('volume') volume: number = 50;
Slider({ value: this.volume })
  .onChange((value) => {
    this.volume = value; // Auto-triggers updates in all components using this state
  })

This approach is 50%+ more concise than Redux/Vuex with near-zero learning cost.

3. Stage Model: True "Write Once, Deploy Multi-Device"

Native Multi-Device Form Factor Support

HarmonyOS Stage Model supports multiple device types at architecture level, not just responsive layout:

// module.json5 configuration
{
  "deviceTypes": [
    "phone",      // Phone
    "tablet",     // Tablet
    "2in1",       // Foldable
    "tv"          // Smart Screen
  ]
}

The author prepared responsive grid layout for future multi-device adaptation:

// Responsive grid layout
Grid() {
  ForEach(this.continents, (item) => {
    GridItem() {
      ContinentCard(item)
    }
  })
}
.columnsTemplate('1fr 1fr')  // Phone: 2 columns
// Future tablet can auto-switch: .columnsTemplate('1fr 1fr 1fr') // Tablet: 3 columns

Device Collaboration Potential

Though current version focuses on phone, Stage Model reserves interfaces for future distributed capabilities:

Phone: Child learns flags on phone
    ↓ One-click flow
Smart Screen: Auto-cast to living room TV, whole family learns together
    ↓ Learning data sync
Tablet: Continue on tablet, seamless progress

Such device collaboration is difficult in iOS/Android ecosystems.

4. DevEco Studio Toolchain: Thoughtful Experience

Intelligent Code Completion

ArkTS code completion accuracy >90%, supporting component property hints, quick Kit module imports, real-time syntax checking. Example: typing Text() triggers auto-suggestions for all properties sorted by usage frequency, drastically reducing documentation lookup time.

Previewer Real-Time Rendering

Previewer enables real-time UI preview without running simulator:

@Preview
@Component
struct ContinentCardPreview {
  build() {
    ContinentCard('亚洲', '48个国家', $r('app.color.continent_asia'))
  }
}

UI updates within 0.5 seconds after save, 10x+ faster than traditional compile-install-run cycle.

5. Ecosystem Dividends: Catching HarmonyOS Explosion Window

Market Opportunity

Official Huawei data (2024): HarmonyOS devices >800M, developers >2.54M (rapid growth), ecosystem covering 18 domains. Early entrants enjoy:

✅ More app market exposure

✅ Strong official support (tech support, traffic bias)

✅ Less competition (scarce quality children's education apps)

Technical Forward-Looking

HarmonyOS has shed AOSP legacy: deeper system optimization, unified design language, long-term evolution roadmap. Choosing HarmonyOS means growing with China's independent OS.

Final Decision: Balance of Technical Ideal and Commercial Reality

Weighted analysis across five dimensions:

Speech Capability (⭐⭐⭐⭐⭐ Core Competitiveness): Native support, best experience → ✅ Decisive Advantage

Development Efficiency (⭐⭐⭐⭐⭐ Impacts Launch Time): 65% Efficiency Gain → ✅ Significant Advantage

Performance Experience (⭐⭐⭐⭐ User Retention Key): Native performance, silky smooth → ✅ Clear Advantage

Multi-Device Extension (⭐⭐⭐ Long-term Value): Stage Model Native Support → ✅ Promising Future

Ecosystem Maturity (⭐⭐⭐ Short-term Risk): Rapidly Growing → ⚠️ Acceptable

Result: Completed in 2 weeks what would have taken 6 weeks.

Core Feature Implementation: Zero-to-One Technical Breakthroughs

Feature 1: Voice Broadcast — Making Flags "Speak"

Challenge: How to Help Young Children Quickly Understand Flag Info?

Traditional text display has high cognitive barrier for 5-8 year olds. Need more intuitive approach.

Solution: Deep Integration of Core Speech Kit TTS

Encapsulated a TextToSpeechManager singleton class. Core code:

// TextToSpeechManager.ets (simplified)
import { textToSpeech } from '@kit.CoreSpeechKit';
export class TextToSpeechManager {
  private static instance: TextToSpeechManager;
  private ttsEngine: textToSpeech.TextToSpeechEngine | null = null;
  // Create TTS Engine
  createEngine() {
    let initParamsInfo: textToSpeech.CreateEngineParams = {
      language: 'zh-CN',
      person: 0,          // Voice: Standard Female
      online: 1,          // Offline Mode
      extraParams: {
        "style": "interaction-broadcast",  // Interactive Broadcast Style
        "locate": "CN"
      }
    };
    textToSpeech.createEngine(initParamsInfo, (err, engine) => {
      if (!err) {
        this.ttsEngine = engine;
        console.info('TTS Engine Created Successfully');
      }
    });
  }
  // Speak Text
  speak(text: string) {
    // Stop current broadcast before starting new
    if (this.ttsEngine?.isBusy()) {
      this.ttsEngine?.stop();
    }
    const speakParams = {
      requestId: new Date().getTime().toString(),
      extraParams: {
        "speed": AppModel.speed,   // User-adjustable speed
        "volume": AppModel.volume, // User-adjustable volume
        "pitch": 1
      }
    };
    this.ttsEngine?.speak(text, speakParams);
  }
}

Technical Highlights

Smart Pause Control : Used [p500] markers for pause control on flag detail page.

const text = `
${country.name}的首都是${country.capital}[p500]货币是${country.currency}[p500]语言是${country.language}
`;
TextToSpeechManager.getInstance().speak(text);

Makes speech more natural, easier for kids to understand.

State Management : isBusy() checks engine status to avoid multiple audio stream conflicts.

User Configurable : Settings page provides volume and speed sliders.

@Builder
SettingsSlider() {
  Row() {
    Text('音量:')
    Slider({ value: this.volume, min: 0, max: 100 })
      .onChange((value) => {
        AppModel.volume = value;
      })
  }
}

Feature 2: Speech Recognition — Let Kids "Speak" Answers

Challenge: How to Verify Children Truly Understand Flag Info?

Traditional multiple-choice or fill-in-blank are operationally complex for young kids. Need more natural interaction.

Solution: Voice Q&A Based on Core Speech Kit ASR

In "Flag Challenge" feature, implemented press-to-speak, release-to-recognize interaction:

// TestDetailPage.ets (Core Logic)
Button() {
  Image($r('app.media.ic_microphone'))
    .height(36)
    .width(36)
}
.onTouch(async (event: TouchEvent) => {
  if (event.type == TouchType.Down) {
    // 🎤 Press: Start Recording Recognition
    await this.requestPermission(async (isAuth) => {
      if (isAuth) {
        await SpeechRecognizerManager.getInstance().startRecording();
      } else {
        this.showPermissionAlert(); // Guide user to authorize
      }
    });
  } else if (event.type == TouchType.Up) {
    // 🛑 Release: Stop Recording, Analyze Result
    SpeechRecognizerManager.getInstance().finishRecognizer();
    this.checkAnswer();
  }
})

Speech Recognition Core Implementation

Encapsulated SpeechRecognizerManager:

export class SpeechRecognizerManager {
  private asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null;
  // Create ASR Engine
  createEngine() {
    let initParamsInfo: speechRecognizer.CreateEngineParams = {
      language: 'zh-CN',
      online: 1,  // Offline Mode
      extraParams: {
        "recognizerMode": "short"   // Short Speech Mode (<60s)
      }
    };
    speechRecognizer.createEngine(initParamsInfo, (err, engine) => {
      if (!err) {
        this.asrEngine = engine;
      }
    });
  }
  // Initialize Listener
  initListener(onResult?: (result: string) => void) {
    const listener = {
      // Recognition Result Callback
      onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) {
        // Remove punctuation for matching
        const cleanText = result.result.replace(/[\p{P}\p{S}]/gu, '');
        if (onResult) onResult(cleanText);
      },
      // Recognition Complete Callback
      onComplete(sessionId: string) {
        console.info('Recognition Complete');
      },
      // Error Callback
      onError(sessionId: string, errorCode: number, errorMessage: string) {
        console.error(`Recognition Failed: ${errorMessage}`);
      }
    };
    this.asrEngine?.setListener(listener);
  }
}

Technical Highlights

Permission Management Optimization : Dynamic microphone permission request via abilityAccessCtrl with friendly guide dialog:

async requestPermission() {
  const atManager = abilityAccessCtrl.createAtManager();
  const result = await atManager.requestPermissionsFromUser(
    this.getUIContext().getHostContext(),
    ["ohos.permission.MICROPHONE"]
  );
  if (result.authResults[0] !== 0) {
    // User denied, show guide dialog
    this.showPermissionGuideDialog();
  }
}

Smart Answer Matching : After removing punctuation, use includes() for fuzzy matching:

handlerAnswer() {
  const isCorrect = this.yourAnswer.includes(this.country.name);
  const result = isCorrect ? '恭喜您,答对了' : '答错了,再想想';
  // Voice + Text Dual Feedback
  SilToast.showToast(this.getUIContext(), result);
  TextToSpeechManager.getInstance().speak(result);
}

Audio Stream Processing : AudioCapturer captures mic stream in real-time, writes to ASR engine:

this.mAudioCapturer.init((dataBuffer: ArrayBuffer) => {
  let uint8Array = new Uint8Array(dataBuffer);
  this.asrEngine?.writeAudio(this.sessionId, uint8Array);
});

Feature 3: Canvas Drawing — Remember Flags by "Hand"

Challenge: How to Deepen Children's Memory of Flags?

Psychology research shows hands-on practice yields 3x+ better memory retention than passive viewing.

Solution: Flag Drawing Based on ArkTS Canvas

// FlagDrawingPage.ets (Core Drawing Logic)
Canvas(this.context)
  .width('100%')
  .height('100%')
  .onTouch((event) => {
    if (event.type === TouchType.Down) {
      this.x = event.touches[0].x;
      this.y = event.touches[0].y;
      this.context.beginPath();
      this.tempPath = new Path2D();
      this.tempPath.moveTo(this.x, this.y);
    }
    if (event.type === TouchType.Move) {
      if (this.isEraserMode) {
        // Eraser Mode
        this.context.clearRect(event.touches[0].x, event.touches[0].y, 20, 20);
      } else {
        // Drawing Mode
        this.context.strokeStyle = this.penColorList[this.penColor];
        this.tempPath.lineTo(event.touches[0].x, event.touches[0].y);
        this.context.stroke(this.tempPath);
      }
    }
  })

Technical Highlights

Real-Time Rendering Optimization : Path2D object caches path, reduces redraws: private tempPath: Path2D = new Path2D(); Shape Tool Support : Basic shapes (rectangle, circle, triangle):

// Draw Circle
if (this.shapeNum === 1) {
  let radius = Math.sqrt((x - x0) ** 2 + (y - y0) ** 2);
  this.context.arc(x0, y0, radius, 0, 360);
  this.context.fillStyle = this.penColor;
  this.context.fill();
}

Reference Image Feature : Displays target flag above canvas to lower difficulty:

Column() {
  Image(this.flagSrc)
    .height('20%')
    .padding(16)
}

Feature 4: Flag Klotski — Fun Puzzle Game

Challenge: How to Keep Learning Process Engaging?

Pure memorization bores kids. Need gamification.

Solution: Configurable Puzzle System Based on ArkTS

// PinTuPage.ets (Configuration-Driven Design)
@StorageLink('GameConfig') gameConfig: FigMessage = {
  w: 1000,        // Image Width
  h: 500,         // Image Height
  rows: 2,        // Rows
  cols: 3,        // Columns
  isOver: false,  // Completion Flag
  imgUrl: $rawfile('images/guoqi/az.png')
};

Technical Highlights

Dynamic Difficulty Adjustment : User selects 2x2, 3x3, etc.:

Grid() {
  ForEach(this.pieces, (piece: Piece) => {
    GridItem() {
      Image(piece.imgSrc)
        .width(this.pieceWidth)
        .height(this.pieceHeight)
    }
    .onClick(() => this.movePiece(piece))
  })
}
.columnsTemplate(this.gameConfig.cols.toString())
.rowsTemplate(this.gameConfig.rows.toString())

Shuffle Algorithm : Fisher-Yates ensures solvable initial state:

shuffle() {
  // Fisher-Yates Shuffle
  for (let i = this.pieces.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [this.pieces[i], this.pieces[j]] = [this.pieces[j], this.pieces[i]];
  }
}

Win Detection : Real-time check if puzzle complete:

checkWin() {
  return this.pieces.every((piece, index) =>
    piece.currentIndex === piece.correctIndex
  );
}

Data Management: Clever Use of AppStorage for State Sharing

Challenge: How to Share User Settings (Volume, Speed) and App State Across Multiple Pages?

HarmonyOS AppStorage solves this perfectly:

// AppModel.ets (Global State Management)
export class AppModel {
  static volume: number = 50;         // Volume
  static speed: number = 1.0;         // Speed
  static firstBroadcast: boolean = true; // First Broadcast Flag
  static currentIndex: number = 0;    // Current Tab Index
  static tabsController = new TabsController();
}
// Usage in Component
@StorageLink('volume') volume: number = 50;
Slider({ value: this.volume })
  .onChange((value) => {
    AppModel.volume = value; // Global Effect
  })

Why Not LocalStorage?

AppStorage is global singleton, suitable for app-level state.

LocalStorage suits page-level state isolation. PersistentStorage suits data needing persistence (e.g., favorites list).

@StorageLink achieves two-way binding; changes sync immediately to all pages.

Performance Optimization: Smooth Display of 200+ Flag Data

App contains 201 countries' flags and details. Optimization strategies:

1. Data Lazy Loading

// Load Only Current Continent's Countries
export function getCountriesByContinent(continent: string): Country[] {
  return COUNTRIES.filter(country => country.continent === continent);
}

2. Virtual List Rendering

List() {
  LazyForEach(this.dataSource, (country: Country) => {
    ListItem() {
      CountryCard(country)
    }
  })
}
.cachedCount(5)  // Cache 5 Items

3. Image Resource Optimization

Use $rawfile to load local resources, avoiding network requests.

Flag images uniformly compressed under 50KB.

Use .objectFit(ImageFit.ScaleDown) to maintain aspect ratio.

Development Insights: Three HarmonyOS Surprises

1. Core Speech Kit Is Truly Powerful

Previously integrating voice required third-party SDKs — complex config, high cost. Now HarmonyOS native offline voice capability eliminates those pains. Mandarin recognition accuracy >95% (real-tested), fully meeting children's education needs.

2. ArkTS Declarative UI Doubles Development Efficiency

Example: Card with rounded corners and shadow in ArkTS:

Column() {
  Text('Card Content')
}
.borderRadius(16)
.shadow({ radius: 8, color: '#1A000000' })

Traditional XML+Code would need at least 10 lines.

3. Stage Model Lifecycle Management Is Clearer

aboutToAppear()

and aboutToDisappear() simplify resource management:

aboutToAppear() {
  TextToSpeechManager.getInstance().createEngine();
}
aboutToDisappear() {
  TextToSpeechManager.getInstance().stop(); // Avoid Memory Leak
}

Future Outlook: Embracing HarmonyOS Ecosystem's Infinite Possibilities

Core features done, but just the beginning. Planned for future versions:

Multi-Modal Interaction : Leverage HarmonyOS device collaboration for phone+tablet dual-screen learning.

AI Capability Upgrade : Integrate HarmonyOS AI Kit for personalized learning recommendations.

Distributed Data Sync : Auto-sync learning progress across devices.

Accessibility Optimization : Deep adapt HarmonyOS accessibility framework for special-needs children.

Conclusion: Technology's Temperature Lies in Serving Every Person

As a developer, I believe good technology should serve people. "Shengqu Guoqitong" is not just a technical practice, but my reflection on "How to Make Children's Education Better with Tech". HarmonyOS's powerful capabilities turned this idea into reality. I truly felt the vitality and potential of the HarmonyOS ecosystem.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performance-optimizationHarmonyOSDeclarative UIArkTSStage ModelCore Speech KitChildren EducationOffline Speech
51CTO HarmonyOS Developer Community
Written by

51CTO HarmonyOS Developer Community

The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.