惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
罗磊的独立博客
月光博客
月光博客
GbyAI
GbyAI
腾讯CDC
G
Google Developers Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
雷峰网
雷峰网
B
Blog RSS Feed
美团技术团队
M
MIT News - Artificial intelligence
有赞技术团队
有赞技术团队
D
Docker

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to implement two-way data synchronization between the...
HarmonyOS · 2026-06-12 · via DEV Community

HarmonyOS

Read the original article:How to implement two-way data synchronization between the app and the widget?

Requirement Description

When users add widgets, the data should be in sync between the app and the widget.

Background Knowledge

  • There may be cases where you want to provide in a widget access to features available in your application running in the foreground, for example, the play, pause, and stop buttons in a music application widget. This is where the call capability of the postCardAction API comes in handy. This capability, when used in a widget, can start the specified UIAbility of the widget provider in the background. It also allows the widget to call the specified method of the application and transfer data so that the application, while in the background, can behave accordingly in response to touching the buttons on the widget.
  • The widget provider can call updateForm to actively update the widget.

Implementation Steps

Step 1: Create an application.

Step 2: Define data models.

Step 3: Implement preferences to store data.

Step 4: Create a widget with dimensions '2*3'

Step 5: Modify MyModeFormAbility to store widget IDs.

Step 6: Modify Entrybility

Step 7: Modify Index.ets to show current mode.

Step 8: Add a widget and see two-way synchronization.

Code Snippet

Step 1: Create an application.

Step 2: Define data models.

  • Mode (Basic enumeration data)
// model/Mode.ets

export enum Mode {
  sad,
  neutral,
  happy
}

  • ModeData and ModeParcelable (For two-way data sync)
// model/ModeParcelable.ets

import { Mode } from "./Mode";
import { rpc } from "@kit.IPCKit";

export interface ModeData {
  mode: Mode
}

export class ModeParcelable implements rpc.Parcelable {
  mode: Mode

  constructor(mode: Mode) {
    this.mode = mode
  }

  marshalling(messageSequence: rpc.MessageSequence): boolean {
    messageSequence.writeInt(this.mode)
    return true;
  }

  unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    const dataObj: ModeData = JSON.parse(messageSequence.readString())
    this.mode = dataObj.mode
    return true;
  }
}

Step 3: Implement preferences to store data.

  • WidgetIdPrefs (To store widget ids)
// util/WidgetIdPrefs.ets

import { Context } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';

export default class WidgetIdPrefs {
  static addFormID(context: Context, formID: string) {
    console.log('addFormID', formID);
    let prefs = preferences.getPreferencesSync(context, { name: 'ids' });
    prefs.putSync(formID, formID)
    prefs.flushSync()
  }

  static removeFormID(context: Context, formID: string) {
    console.log('removeFormID', formID);
    let prefs = preferences.getPreferencesSync(context, { name: 'ids' });
    prefs.deleteSync(formID)
    prefs.flushSync()
  }

  static getFormIDs(context: Context): string[] {
    console.log('getFormIDs');
    let prefs = preferences.getPreferencesSync(context, { name: 'ids' });
    return Object.values(prefs.getAllSync())
  }
}

  • ModePrefs (to store current mode)
// util/ModePrefs.ets

import { Context } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';
import { Mode } from '../model/Mode';

export default class ModePrefs {
  static setMode(context: Context, newMode: Mode) {
    let prefs = preferences.getPreferencesSync(context, { name: 'mode_data' });
    prefs.putSync('mode', newMode)
    prefs.flushSync()
  }

  static getMode(context: Context): Mode {
    let prefs = preferences.getPreferencesSync(context, { name: 'mode_data' });
    return prefs.getSync('mode', Mode.happy) as Mode
  }
}

Step 4: Create a widget with dimensions '2*3' (This can be added to the cards page of the watch) and name it MyMode. For details about how to create a widget, please visit: Creating an ArkTS Widget

  • MyModeCard
// mymode/pages/MyModeCard.ets

import { Mode } from '../../model/Mode'

let ls = new LocalStorage();

@Entry(ls)
@Component
struct MyModeCard {
  @LocalStorageProp('mode') mode: Mode = Mode.happy

  build() {
    Row({ space: 8 }) {
      Button('😭')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.sad ? Color.Red : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.sad)
        })
      Button('😑')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.neutral ? Color.Yellow : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.neutral)
        })
      Button('😊')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.happy ? Color.Green : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.happy)
        })
    }
    .padding(8)
    .height('100%')
    .backgroundColor($r('sys.color.comp_background_primary'))
    .onClick(() => {
      postCardAction(this, {
        action: 'router',
        abilityName: 'EntryAbility',
      });
    })
  }

  changeMode(newMode: Mode) {
    if (this.mode !== newMode) {
      this.mode = newMode
      this.postCardActionCall(newMode)
    }
  }

  // call action to wake app
  postCardActionCall(newMode: Mode) {
    postCardAction(this, {
      action: 'call',
      abilityName: 'EntryAbility',
      params: {
        // function name
        method: 'changeMode',
        // additional parameters
        mode: newMode
      }
    });
  }
}

Step 5: Modify MyModeFormAbility to store widget IDs.

  • MyModeFormAbility
// mymodeformability/MyModeFormAbility.ets

onAddForm(want: Want) {
  // Called to return a FormBindingData object.
  if (!want || !want.parameters) {
    console.error(`FormAbility onAddForm want or want.parameters is undefined`);
    return formBindingData.createFormBindingData('');
  }

  let formId: string = want.parameters[formInfo.FormParam.IDENTITY_KEY] as string;

  // store form id
  WidgetIdPrefs.addFormID(this.context, formId);
  console.log('set form id', formId)

  const data: ModeData = { mode: ModePrefs.getMode(this.context) };

  // send initial data to the widget
  return formBindingData.createFormBindingData(data);
}

onRemoveForm(formId: string) {
  // Called to notify the form provider that a specified form has been destroyed.
  // remove form id
  WidgetIdPrefs.removeFormID(this.context, formId);
}

Step 6: Modify Entrybility

  • to catch the call action.
// entryability/EntryAbility.ets

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');

  // postCardAction 'call' is caught here
  this.callee.on('changeMode', (data: rpc.MessageSequence) => {
    // Obtain all parameters passed in the call event.
    const dataObj: ModeData = JSON.parse(data.readString())
    console.log('ModeData', JSON.stringify(dataObj), dataObj.mode);

    AppStorage.set('mode', dataObj.mode)
    ModePrefs.setMode(this.context, dataObj.mode);
    vibrator.startVibration({ type: 'time', duration: 200 }, { usage: 'physicalFeedback' })
    return new ModeParcelable(dataObj.mode);
  });
}

  • to store mode in PersistentStorage (to update UI directly)
onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
      return;
    }
    hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');

    const mode = ModePrefs.getMode(this.context)
    PersistentStorage.persistProp('mode', mode)
    AppStorage.set('mode', mode)
  });
}

Step 7: Modify Index.ets to show current mode.

  • Index
import { Mode } from '../model/Mode';
import { ModeData } from '../model/ModeParcelable';
import ModePrefs from '../util/ModePrefs';
import WidgetIdPrefs from '../util/WidgetIdPrefs';
import { formBindingData, formProvider } from '@kit.FormKit';


@Entry
@Component
struct Index {
  @StorageLink('mode') mode: Mode = ModePrefs.getMode(this.getUIContext().getHostContext() as Context)

  build() {
    Row({ space: 8 }) {
      Button('😭')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.sad ? Color.Red : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.sad)
        })
      Button('😑')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.neutral ? Color.Yellow : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.neutral)
        })
      Button('😊')
        .fontSize(18)
        .backgroundColor(this.mode === Mode.happy ? Color.Green : Color.Grey)
        .type(ButtonType.Circle)
        .layoutWeight(1)
        .onClick(() => {
          this.changeMode(Mode.happy)
        })
    }
    .padding(8)
    .height('100%')
    .backgroundColor($r('sys.color.comp_background_primary'))
  }

  changeMode(newMode: Mode) {
    if (this.mode !== newMode) {
      this.mode = newMode
      ModePrefs.setMode(this.getUIContext().getHostContext() as Context, newMode)
      const ids = WidgetIdPrefs.getFormIDs(this.getUIContext().getHostContext() as Context)
      // update widgets
      ids.forEach((id: string) => {
        const data: ModeData = { 'mode': newMode }
        let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(data);
        formProvider.updateForm(id, formInfo)
      })
    }
  }
}

Step 8: Add a widget and see two-way synchronization.

Written by Mehmet Karaaslan