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.
Top comments (0)