未验证 提交 7d79e069 编写于 作者: O openharmony_ci 提交者: Gitee

!18196 翻译完成...

!18196 翻译完成 17018+17193+17052+17530+17474+17164+17023:Quick-start文件夹更新+[ArkTS卡片]增加ArkTS卡片开发指南+开发指南新增角标和粘性事件移除
Merge pull request !18196 from ester.zhou/TR-C0515
......@@ -17,11 +17,36 @@
- ExtensionAbility Component
- [ExtensionAbility Component Overview](extensionability-overview.md)
- [ServiceExtensionAbility](serviceextensionability.md)
- [FormExtensionAbility (Widget)](widget-development-stage.md)
- [DataShareExtensionAbility (for System Applications Only)](datashareextensionability.md)
- [AccessibilityExtensionAbility](accessibilityextensionability.md)
- [EnterpriseAdminExtensionAbility](enterprise-extensionAbility.md)
- [InputMethodExtensionAbility](inputmethodextentionability.md)
- [WindowExtensionAbility](windowextensionability.md)
- Service Widget Development in Stage Model
- [Service Widget Overview](service-widget-overview.md)
- Developing an ArkTS Widget
- [ArkTS Widget Working Principles](arkts-ui-widget-working-principles.md)
- [ArkTS Widget Related Modules](arkts-ui-widget-modules.md)
- ArkTS Widget Development
- [Creating an ArkTS Widget](arkts-ui-widget-creation.md)
- [Configuring Widget Configuration Files](arkts-ui-widget-configuration.md)
- [Widget Lifecycle Management](arkts-ui-widget-lifecycle.md)
- Widget Page Development
- [Widget Page Capability Overview](arkts-ui-widget-page-overview.md)
- [Using Animations in the Widget](arkts-ui-widget-page-animation.md)
- [Applying Custom Drawing in the Widget](arkts-ui-widget-page-custom-drawing.md)
- Widget Event Development
- [Widget Event Capability Overview](arkts-ui-widget-event-overview.md)
- [Updating Widget Content Through FormExtensionAbility](arkts-ui-widget-event-formextensionability.md)
- [Updating Widget Content Through UIAbility](arkts-ui-widget-event-uiability.md)
- [Redirecting to a Specified Page Through the Router Event](arkts-ui-widget-event-router.md)
- Widget Data Interaction
- [Widget Data Interaction Overview](arkts-ui-widget-interaction-overview.md)
- [Configuring a Widget to Update Periodically](arkts-ui-widget-update-by-time.md)
- [Updating Local and Online Images in the Widget](arkts-ui-widget-image-update.md)
- [Updating Widget Content by State](arkts-ui-widget-update-by-status.md)
- [Updating Widget Content by Widget Host (for System Applications Only)](arkts-ui-widget-content-update.md)
- [Developing a JS Widget](js-ui-widget-development.md)
- [AbilityStage Component Container](abilitystage.md)
- [Context](application-context-stage.md)
- Want
......@@ -47,6 +72,7 @@
- [Subscribing to Common Events in Static Mode (for System Applications Only)](common-event-static-subscription.md)
- [Unsubscribing from Common Events](common-event-unsubscription.md)
- [Publishing Common Events](common-event-publish.md)
- [Removing Sticky Common Events](common-event-remove-sticky.md)
- [Background Services](background-services.md)
- Inter-Thread Communication
- [Thread Model](thread-model-stage.md)
......
# Configuring Widget Configuration Files
Widget-related configuration includes **FormExtensionAbility** configuration and widget configuration.
1. Configure FormExtensionAbility information under **extensionAbilities** in the [module.json5 file](../quick-start/module-configuration-file.md). For a FormExtensionAbility, you must specify **metadata**. Specifically, set **name** to **ohos.extension.form** (fixed), and set **resource** to the index of the widget configuration information.
Example configuration:
```json
{
"module": {
...
"extensionAbilities": [
{
"name": "EntryFormAbility",
"srcEntry": "./ets/entryformability/EntryFormAbility.ts",
"label": "$string:EntryFormAbility_label",
"description": "$string:EntryFormAbility_desc",
"type": "form",
"metadata": [
{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}
]
}
]
}
}
```
2. Configure the widget configuration information. In the **metadata** configuration item of FormExtensionAbility, you can specify the resource index of specific configuration information of the widget. For example, if resource is set to **$profile:form_config**, **form_config.json** in the **resources/base/profile/** directory of the development view is used as the profile configuration file of the widget. The following table describes the internal field structure.
**Table 1** form_config.json file
| Field| Description| Data Type| Default Value Allowed|
| -------- | -------- | -------- | -------- |
| name | Class name of the widget. The value is a string with a maximum of 127 bytes.| String| No|
| description | Description of the widget. The value can be a string or a resource index to descriptions in multiple languages. The value is a string with a maximum of 255 bytes.| String| Yes (initial value: left empty)|
| src | Full path of the UI code corresponding to the widget. For an ArkTS widget, the full path must contain the widget file name extension, for example, **./ets/widget/pages/WidgetCard.ets**. For a JS widget, the full path does not need to contain the widget file name extension, for example, **./js/widget/pages/WidgetCard**.| String| No|
| uiSyntax | Type of the widget.<br>- **arkts**: ArkTS widget<br>- **hml**: JS widget| String| Yes (initial value: **hml**)|
| window | Window-related configurations.| Object| Yes|
| isDefault | Whether the widget is a default one. Each UIAbility has only one default widget.<br>- **true**: The widget is the default one.<br>- **false**: The widget is not the default one.| Boolean| No|
| colorMode | Color mode of the widget.<br>- **auto**: auto-adaptive color mode<br>- **dark**: dark color mode<br>- **light**: light color mode| String| Yes (initial value: **auto**)|
| supportDimensions | Grid styles supported by the widget.<br>- **1 * 2**: indicates a grid with one row and two columns.<br>- **2 * 2**: indicates a grid with two rows and two columns.<br>- **2 * 4**: indicates a grid with two rows and four columns.<br>- **4 * 4**: indicates a grid with four rows and four columns.| String array| No|
| defaultDimension | Default grid style of the widget. The value must be available in the **supportDimensions** array of the widget.| String| No|
| updateEnabled | Whether the widget can be updated periodically.<br>- **true**: The widget can be updated at a specified interval (**updateDuration**) or at the scheduled time (**scheduledUpdateTime**). **updateDuration** takes precedence over **scheduledUpdateTime**.<br>- **false**: The widget cannot be updated periodically.| Boolean| No|
| scheduledUpdateTime | Scheduled time to update the widget. The value is in 24-hour format and accurate to minute.<br>**NOTE**<br>**updateDuration** takes precedence over **scheduledUpdateTime**. If both are specified, the value specified by **updateDuration** is used.| String| Yes (initial value: The widget cannot be updated periodically.)|
| updateDuration | Interval to update the widget. The value is a natural number, in the unit of 30 minutes.<br>If the value is **0**, this field does not take effect.<br>If the value is a positive integer *N*, the interval is calculated by multiplying *N* and 30 minutes.<br>**NOTE**<br>**updateDuration** takes precedence over **scheduledUpdateTime**. If both are specified, the value specified by **updateDuration** is used.| Number| Yes (initial value: **0**)|
| formConfigAbility | Link to a specific page of the application. The value is a URI.| String| Yes (initial value: left empty)|
| formVisibleNotify | Whether the widget is allowed to use the widget visibility notification.| String| Yes (initial value: left empty)|
| metadata | Metadata of the widget. This field contains the array of the **customizeData** field.| Object| Yes (initial value: left empty)|
Example configuration:
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": [
"2*2"
]
}
]
}
```
# Updating Widget Content by Widget Host (for System Applications Only)
Widgets that are updated periodically are subject to the scheduled time or interval settings. To offer more flexible updates, the widget host can provide a button to proactively trigger a widget update. Specifically, the widget host calls the [requestForm](../reference/apis/js-apis-app-form-formHost.md#requestform) API to request a widget update. The system then calls the [onUpdateForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onupdateform) lifecycle callback in the FormExtensionAbility of the widget provider. In the callback, the [updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform) API can be used to update the widget content. For details about the **onUpdateForm** lifecycle callback, see [Updating Widget Content Through FormExtensionAbility](arkts-ui-widget-event-formextensionability.md).
```ts
import formHost from '@ohos.app.form.formHost';
@Entry()
@Component
struct WidgetCard {
formId = ...; // Widget ID
build() {
Button (`Update Widget`)
.type(ButtonType.Capsule)
.width('50%')
.height(50)
.onClick(() => {
console.info('FormAbility update form click');
// formId is the ID of the widget to be updated.
formHost.requestForm(this.formId.toString()).then(() => {
console.info('Succeeded in requestForming.');
});
})
...
}
}
```
# Creating an ArkTS Widget
To create an ArkTS widget in an existing application project, perform the following steps:
1. Create a widget.
![WidgetProjectCreate1](figures/WidgetProjectCreate1.png)
2. Select a widget template based on the actual service scenario.
![WidgetProjectCreate2](figures/WidgetProjectCreate2.png)
3. Set **Language** to **ArkTS** and click **Finish**.
![WidgetProjectCreate3](figures/WidgetProjectCreate3.png)
After an ArkTS widget is created, the following widget-related files are added to the project directory: **EntryFormAbility.ts** (widget lifecycle management file), **WidgetCard.ets** (widget page file), and **form_config.json** (widget configuration file).
![WidgetProjectView](figures/WidgetProjectView.png)
# Updating Widget Content Through FormExtensionAbility
On the widget page, the **postCardAction** API can be used to trigger a message event to the FormExtensionAbility, which then updates the widget content. The following is an example of this widget update mode.
- On the widget page, register the **onClick** event callback of the button and call the **postCardAction** API in the callback to trigger the event to the FormExtensionAbility.
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('title') title: string = 'init';
@LocalStorageProp('detail') detail: string = 'init';
build() {
Column() {
Button ('Update')
.onClick(() => {
postCardAction(this, {
'action': 'message',
'params': {
'msgTest': 'messageEvent'
}
});
})
Text(`${this.title}`)
Text(`${this.detail}`)
}
.width('100%')
.height('100%')
}
}
```
- Call the [updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform) API to update the widget in the **onFormEvent** callback of the FormExtensionAbility.
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onFormEvent(formId, message) {
// Called when a specified message event defined by the form provider is triggered.
console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
let formData = {
'title':'Title Update Success.', // Matches the widget layout.
'detail':'Detail Update Success.', // Matches the widget layout.
};
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
// ...
}
```
The figure below shows the effect.
![WidgetUpdatePage](figures/WidgetUpdatePage.png)
# Widget Event Capability Overview
The ArkTS widget provides the **postCardAction()** API for interaction between the widget internal and the provider application. Currently, this API supports the router, message, and call events and can be called only in the widget.
![WidgetPostCardAction](figures/WidgetPostCardAction.png)
Definition: postCardAction(component: Object, action: Object): void
Parameters:
| Name| Type| Mandatory| Description|
| -------- | -------- | -------- | -------- |
| component | Object | Yes| Instance of the current custom component. Generally, **this** is transferred.|
| action | Object | Yes| Action description. For details, see the following table.|
Description of the action parameter
| **Key** | **Value** | Description|
| -------- | -------- | -------- |
| "action" | string | Action type.<br>- **"router"**: application redirection. If this type of action is triggered, the corresponding UIAbility is displayed. Only the UIAbility of the current application can be displayed.<br>- **"message"**: custom message. If this type of action is triggered, the [onFormEvent()](../reference/apis/js-apis-app-form-formExtensionAbility.md#onformevent) lifecycle callback of the provider FormExtensionAbility is called.<br>- **"call"**: application startup in the background. If this type of action is triggered, the corresponding UIAbility is started but does not run in the foreground. The target application must have the permission to run in the background ([ohos.permission.KEEP_BACKGROUND_RUNNING](../security/permission-list.md#ohospermissionkeep_background_running)).|
| "bundleName" | string | Name of the target bundle when **action** is **"router"** or **"call"**. This parameter is optional.|
| "moduleName" | string | Name of the target module when **action** is **"router"** or **"call"**. This parameter is optional.|
| "abilityName" | string | Name of the target UIAbility when **action** is **"router"** or **"call"**. This parameter is mandatory.|
| "params" | Object | Additional parameters carried in the current action. The value is a key-value pair in JSON format.|
Sample code of the **postCardAction()** API:
```typescript
Button ('Jump')
.width('40%')
.height('20%')
.onClick(() => {
postCardAction(this, {
'action': 'router',
'bundleName': 'com.example.myapplication',
'abilityName': 'EntryAbility',
'params': {
'message': 'testForRouter' // Customize the message to be sent.
}
});
})
```
The following are typical widget development scenarios that can be implemented through widget events:
- [Updating Widget Content Through FormExtensionAbility](arkts-ui-widget-event-formextensionability.md)
- [Updating Widget Content Through UIAbility](arkts-ui-widget-event-uiability.md)
- [Redirecting to a Specified Page Through the Router Event](arkts-ui-widget-event-router.md)
# Redirecting to a Specified Page Through the Router Event
The **router** capability of the **postCardAction** API can be used in a widget to quickly start the widget provider application. An application can provide different buttons through the widget so that users can jump to different pages at the touch of a button. For example, a camera widget provides the buttons that direct the user to respective pages, such as the page for taking a photo and the page for recording a video.
![WidgerCameraCard](figures/WidgerCameraCard.png)
Generally, a button is used to start a page.
- Design two buttons on the widget page. When one of the buttons is clicked, **postCardAction** is called to send a router event to the specified UIAbility, with the content to be transferred defined in the event.
```ts
@Entry
@Component
struct WidgetCard {
build() {
Column() {
Button ('Function A')
.margin('20%')
.onClick(() => {
console.info('Jump to EntryAbility funA');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // Only the UIAbility of the current application is allowed.
'params': {
'targetPage': 'funA' // Process the information in the EntryAbility.
}
});
})
Button ('Function B')
.margin('20%')
.onClick(() => {
console.info('Jump to EntryAbility funB');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // Only the UIAbility of the current application is allowed.
'params': {
'targetPage': 'funB' // Process the information in the EntryAbility.
}
});
})
}
.width('100%')
.height('100%')
}
}
```
- The UIAbility receives the router event and obtains parameters. It then starts the page specified in the received message.
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
let selectPage = "";
let currentWindowStage = null;
export default class CameraAbility extends UIAbility {
// If the UIAbility is started for the first time, the onCreate lifecycle callback is triggered after the router event is received.
onCreate(want, launchParam) {
// Obtain the targetPage parameter passed in the router event.
console.info("onCreate want:" + JSON.stringify(want));
if (want.parameters.params !== undefined) {
let params = JSON.parse(want.parameters.params);
console.info("onCreate router targetPage:" + params.targetPage);
selectPage = params.targetPage;
}
}
// If the UIAbility is running in the background, the onNewWant lifecycle callback is triggered after the router event is received.
onNewWant(want, launchParam) {
console.info("onNewWant want:" + JSON.stringify(want));
if (want.parameters.params !== undefined) {
let params = JSON.parse(want.parameters.params);
console.info("onNewWant router targetPage:" + params.targetPage);
selectPage = params.targetPage;
}
if (currentWindowStage != null) {
this.onWindowStageCreate(currentWindowStage);
}
}
onWindowStageCreate(windowStage: window.WindowStage) {
let targetPage;
// Start the page specified by targetPage.
switch (selectPage) {
case 'funA':
targetPage = 'pages/FunA';
break;
case 'funB':
targetPage = 'pages/FunB';
break;
default:
targetPage = 'pages/Index';
}
if (currentWindowStage === null) {
currentWindowStage = windowStage;
}
windowStage.loadContent(targetPage, (err, data) => {
if (err && err.code) {
console.info('Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
});
}
};
```
# Updating Widget Content Through UIAbility
On the widget page, the **postCardAction** API can be used to trigger a router or call event to start the UIAbility, which then updates the widget content. The following is an example of this widget update mode.
- On the widget page, register the **onClick** event callback of the button and call the **postCardAction** API in the callback to trigger the event to the FormExtensionAbility.
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('detail') detail: string = 'init';
build() {
Column() {
Button ('Jump')
.margin('20%')
.onClick(() => {
console.info('postCardAction to EntryAbility');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // Only the UIAbility of the current application is allowed.
'params': {
'detail': 'RouterFromCard'
}
});
})
Text(`${this.detail}`).margin('20%')
}
.width('100%')
.height('100%')
}
}
```
- In the **onCreate()** or **onNewWant()** lifecycle callback of the UIAbility, use the input parameter **want** to obtain the ID (**formID**) and other information of the widget, and then call the [updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform) API to update the widget.
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import formInfo from '@ohos.app.form.formInfo';
export default class EntryAbility extends UIAbility {
// If the UIAbility is started for the first time, the onCreate lifecycle callback is triggered after the router event is received.
onCreate(want, launchParam) {
console.info('Want:' + JSON.stringify(want));
if (want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
let curFormId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let message = JSON.parse(want.parameters.params).detail;
console.info(`UpdateForm formId: ${curFormId}, message: ${message}`);
let formData = {
"detail": message +': onCreate UIAbility.', // Matches the widget layout.
};
let formMsg = formBindingData.createFormBindingData(formData)
formProvider.updateForm(curFormId, formMsg).then((data) => {
console.info('updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('updateForm failed:' + JSON.stringify(error));
})
}
}
// If the UIAbility is running in the background, the onNewWant lifecycle callback is triggered after the router event is received.
onNewWant(want, launchParam) {
console.info('onNewWant Want:' + JSON.stringify(want));
if (want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
let curFormId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let message = JSON.parse(want.parameters.params).detail;
console.info(`UpdateForm formId: ${curFormId}, message: ${message}`);
let formData = {
"detail": message +': onNewWant UIAbility.', // Matches the widget layout.
};
let formMsg = formBindingData.createFormBindingData(formData)
formProvider.updateForm(curFormId, formMsg).then((data) => {
console.info('updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('updateForm failed:' + JSON.stringify(error));
})
}
}
...
}
```
# Updating Local and Online Images in the Widget
Generally, local images or online images downloaded from the network need to be displayed on a widget. To obtain local and online images, use the FormExtensionAbility. The following exemplifies how to show local and online images on a widget.
1. Internet access is required for downloading online images. Therefore, you need to apply for the **ohos.permission.INTERNET** permission. For details, see[Declaring Permissions in the Configuration File](../security/accesstoken-guidelines.md).
2. Update local files in the **onAddForm** lifecycle callback of the EntryFormAbility.
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import request from '@ohos.request';
import fs from '@ohos.file.fs';
export default class EntryFormAbility extends FormExtensionAbility {
...
// When the widget is added, a local image is opened and transferred to the widget page for display.
onAddForm(want) {
// Assume that the local image head.PNG is in the tmp directory of the current widget.
let tempDir = this.context.getApplicationContext().tempDir;
// Open the local image and obtain the FD after the image is opened.
let file;
try {
file = fs.openSync(tempDir + '/' + 'head.PNG');
} catch (e) {
console.error(`openSync failed: ${JSON.stringify(e)}`);
}
let formData = {
'text': 'Image: Bear',
'imgName': 'imgBear',
'formImages': {
'imgBear': file.fd
},
'loaded': true
}
// Encapsulate the FD in formData and return it to the widget page.
return formBindingData.createFormBindingData(formData);
}
...
}
```
3. Update online files in the onFormEvent lifecycle callback of the EntryFormAbility.
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import request from '@ohos.request';
import fs from '@ohos.file.fs';
export default class EntryFormAbility extends FormExtensionAbility {
// When the message event is triggered on the widget page, an online image is downloaded and transferred to the widget page for display.
onFormEvent(formId, message) {
let formInfo = formBindingData.createFormBindingData({
'text': 'Updating...'
})
formProvider.updateForm(formId, formInfo)
// Note: The FormExtensionAbility is started when the lifecycle callback is triggered. It can run in the background for only 5 seconds.
// When possible, limit the size of the image to download. If an image cannot be downloaded within 5 seconds, it cannot be updated to the widget page.
let netFile = 'https://xxxx/xxxx.png'; // Specify the URL of the image to download.
let tempDir = this.context.getApplicationContext().tempDir;
let fileName = 'file' + Date.now();
let tmpFile = tempDir + '/' + fileName;
request.downloadFile(this.context, {
url: netFile, filePath: tmpFile, enableMetered: true, enableRoaming: true
}).then((task) => {
task.on('complete', function callback() {
console.info('ArkTSCard download complete:' + tmpFile);
let file;
try {
file = fs.openSync(tmpFile);
} catch (e) {
console.error(`openSync failed: ${JSON.stringify(e)}`);
}
let fileInfo = {};
fileInfo[fileName] = file.fd;
let formData = {
'text': 'Image:' + fileName,
'imgName': fileName,
'formImages': fileInfo,
'loaded': true
};
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
})
task.on('fail', function callBack(err) {
console.info('ArkTSCard download task failed. Cause:' + err);
let formInfo = formBindingData.createFormBindingData({
'text':'Update failed.'
})
formProvider.updateForm(formId, formInfo)
});
}).catch((err) => {
console.error('Failed to request the download. Cause: ' + JSON.stringify(err));
});
}
...
};
```
4. On the widget page, use the **\<Image>** component to display the widget content transferred from the EntryFormAbility.
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('text') text: string = 'Loading...';
@LocalStorageProp('loaded') loaded: boolean = false;
@LocalStorageProp('imgName') imgName: string = 'name';
build() {
Column() {
Text(this.text)
.fontSize('12vp')
.textAlign(TextAlign.Center)
.width('100%')
.height('15%')
Row() {
if (this.loaded) {
Image('memory://' + this.imgName)
.width('50%')
.height('50%')
.margin('5%')
} else {
Image('common/start.PNG')
.width('50%')
.height('50%')
.margin('5%')
}
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
Button ('Update')
.height('15%')
.onClick(() => {
postCardAction(this, {
'action': 'message',
'params': {
'info': 'refreshImage'
}
});
})
}
.width('100%').height('100%')
.alignItems(HorizontalAlign.Center)
.padding('5%')
}
}
```
> **NOTE**
> - The **\<Image>** component displays images in the remote memory based on the **memory://** identifier in the input parameter (**memory://fileName**). The **fileName** value must be consistent with the key in the object (**'formImages': {key: fd}**) passed by the EntryFormAbility.
>
> - The **\<Image>** component determines whether to update the image based on whether the input parameter is changed. Therefore, the value of **imgName** passed by the EntryFormAbility each time must be different. If the two values of **imgName** passed consecutively are identical, the image is not updated.
# Widget Data Interaction
The ArkTS widget framework provides the **updateForm()** and **requestForm()** APIs to proactively trigger widget updates.
![WidgetLocalStorageProp](figures/WidgetLocalStorageProp.png)
| API| System Capability| Constraints|
| -------- | -------- | -------- |
| updateForm | No| 1. Invoked by the provider.<br>2. Allows only the widget provider to update its own widgets. It cannot be used to update widgets by other providers.|
| requestForm | Yes| 1. Invoked by the host.<br>2. Allows only the widget host to update the widgets added to it. It cannot be used to update widgets added to other hosts.|
The following describes the typical use cases of widget updates:
- [Configuring a Widget to Update Periodically](arkts-ui-widget-update-by-time.md)
- [Updating Local and Online Images](arkts-ui-widget-image-update.md)
- [Updating Widget Content by State](arkts-ui-widget-update-by-status.md)
- [Updating Widget Content by Widget Host (for System Applications Only)](arkts-ui-widget-content-update.md)
# Widget Lifecycle Management
When creating an ArkTS widget, you need to implement the [FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md) lifecycle APIs.
1. Import related modules to **EntryFormAbility.ts**.
```ts
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
```
2. In **EntryFormAbility.ts**, implement the [FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md) lifecycle APIs, including **onAddForm**, whose **want** parameter can be used to obtain the widget information through [FormParam](../reference/apis/js-apis-app-form-formInfo.md#formparam).
```typescript
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want) {
console.info('[EntryFormAbility] onAddForm');
// Obtain the unique widget ID formId from the want parameter.
let formId: string = want.parameters[formInfo.FormParam.IDENTITY_KEY];
// Called when the widget is created. The widget provider should return the widget data binding class.
let obj = {
'title': 'titleOnAddForm',
'detail': 'detailOnAddForm'
};
let formData = formBindingData.createFormBindingData(obj);
return formData;
}
onCastToNormalForm(formId) {
// Called when the form provider is notified that a temporary form is successfully
// converted to a normal form.
// Called when the widget host converts the temporary widget into a normal one. The widget provider should do something to respond to the conversion.
console.info(`[EntryFormAbility] onCastToNormalForm, formId: ${formId}`);
}
onUpdateForm(formId) {
// Override this method to support scheduled updates, periodic updates, or updates requested by the widget host.
console.info('[EntryFormAbility] onUpdateForm');
let obj = {
'title': 'titleOnUpdateForm',
'detail': 'detailOnUpdateForm'
};
let formData = formBindingData.createFormBindingData(obj);
formProvider.updateForm(formId, formData).catch((err) => {
if (err) {
// Print errors.
console.error(`[EntryFormAbility] Failed to updateForm. Code: ${err.code}, message: ${err.message}`);
return;
}
});
}
onChangeFormVisibility(newStatus) {
// Called when the form provider receives form events from the system.
// The callback is performed only when formVisibleNotify is set to true and the application is a system application.
console.info('[EntryFormAbility] onChangeFormVisibility');
}
onFormEvent(formId, message) {
// Called when a specified message event defined by the form provider is triggered.
// If the widget supports event triggering, override this method and implement the trigger.
console.info('[EntryFormAbility] onFormEvent');
}
onRemoveForm(formId) {
// Called to notify the form provider that a specified form has been destroyed.
// Called when the corresponding widget is deleted. The input parameter is the ID of the deleted card.
console.info('[EntryFormAbility] onRemoveForm');
}
onConfigurationUpdate(config) {
// Called when the system configuration is updated.
console.info('[EntryFormAbility] configurationUpdate:' + JSON.stringify(config));
}
onAcquireFormState(want) {
// Called to return a {@link FormState} object.
// Called when the widget provider receives the status query result of a widget. By default, the initial state of the widget is returned.
return formInfo.FormState.READY;
}
}
```
> **NOTE**
> The FormExtensionAbility cannot reside in the background. Therefore, continuous tasks cannot be processed in the widget lifecycle callbacks. The FormExtensionAbility persists for 5 seconds after the lifecycle callback is completed and will exit if no new lifecycle callback is invoked during this time frame. For the service logic that may take more than 5 seconds to complete, it is recommended that you [start the application](arkts-ui-widget-event-uiability.md). After the processing is complete, use the [updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform) to notify the widget of the update.
# ArkTS Widget Related Modules
**Figure 1** ArkTS widget related modules
![WidgetModules](figures/WidgetModules.png)
- [FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md): provides lifecycle callbacks invoked when a widget is created, destroyed, or updated.
- [FormExtensionContext](../reference/apis/js-apis-inner-application-formExtensionContext.md): provides context for FormExtensionAbilities. You can use the APIs of this module to start FormExtensionAbilities.
- [formProvider](../reference/apis/js-apis-app-form-formProvider.md): provides APIs related to the widget provider. You can use the APIs to update a widget, set the next update time for a widget, obtain widget information, and request a widget release.
- [formInfo](../reference/apis/js-apis-app-form-formInfo.md): provides types and enums related to the widget information and state.
- [formBindingData](../reference/apis/js-apis-app-form-formBindingData.md): provides APIs for widget data binding. You can use the APIs to create a **FormBindingData** object and obtain related information.
- [Page Layout (Card.ets)](arkts-ui-widget-page-overview.md): provides APIs for a declarative paradigm UI.
- [ArkTS widget capabilities](arkts-ui-widget-event-overview.md): include the **postCardAction** API used for interaction between the widget internal and the provider application and can be called only in the widget.
- [ArkTS widget capability list](arkts-ui-widget-page-overview.md#page-capabilities-supported-by-arkts-widgets): lists the APIs, components, events, attributes, and lifecycle callbacks that can be used in ArkTS widgets.
- [Widget configuration](arkts-ui-widget-configuration.md): includes FormExtensionAbility configuration and widget configuration.
- Configure FormExtensionAbility information under **extensionAbilities** in the [module.json5 file](../quick-start/module-configuration-file.md).
- Configure the widget configuration information (**WidgetCard.ets**) in the [form_config.json](arkts-ui-widget-configuration.md) file in **resources/base/profile**.
# Using Animations in the Widget
To make your ArkTS widget more engaging, you can apply animations to it, including [explicit animation](../reference/arkui-ts/ts-explicit-animation.md), [attribute animation](../reference/arkui-ts/ts-animatorproperty.md), and [component transition](../reference/arkui-ts/ts-transition-animation-component.md). Note the following restrictions when using the animations in ArkTS widgets.
**Table 1** Restrictions on animation parameters
| Name| Description| Description|
| -------- | -------- | -------- |
| duration | Animation playback duration| The maximum value is 1 second. If a larger value is set, the animation is still played for 1 second.|
| tempo | Animation playback speed.| Do not set this parameter in the widget. Use the default value 1.|
| delay | Animation delay duration.| Do not set this parameter in the widget. Use the default value 0.|
| iterations | Number of times that the animation is played.| Do not set this parameter in the widget. Use the default value 1.|
The following sample code implements the animation effect of button rotation:
![WidgetAnimation](figures/WidgetAnimation.gif)
```ts
@Entry
@Component
struct AttrAnimationExample {
@State rotateAngle: number = 0;
build() {
Column() {
Button('change rotate angle')
.onClick(() => {
this.rotateAngle = 90;
})
.margin(50)
.rotate({ angle: this.rotateAngle })
.animation({
curve: Curve.EaseOut,
playMode: PlayMode.AlternateReverse
})
}.width('100%').margin({ top: 20 })
}
}
```
# Applying Custom Drawing in the Widget
You can apply custom drawing in your ArkTS widget to create a more vibrant experience. Use the [Canvas](../reference/arkui-ts/ts-components-canvas-canvas.md) component to create a canvas on the widget, and then use the [CanvasRenderingContext2D](../reference/arkui-ts/ts-canvasrenderingcontext2d.md) object to draw custom graphics on the canvas. The following code shows how to draw a smiling face in the center of the canvas.
```typescript
@Entry
@Component
struct Card {
private canvasWidth: number = 0;
private canvasHeight: number = 0;
// Initialize CanvasRenderingContext2D and RenderingContextSettings.
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
Row() {
Canvas(this.context)
.margin('5%')
.width('90%')
.height('90%')
.onReady(() => {
console.info('[ArkTSCard] onReady for canvas draw content');
// Obtain the actual width and height of the canvas in the onReady callback.
this.canvasWidth = this.context.width;
this.canvasHeight = this.context.height;
// Draw the background of the canvas.
this.context.fillStyle = 'rgba(203, 154, 126, 1.00)';
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
// Draw a red circle in the center of the canvas.
this.context.beginPath();
let radius = this.context.width / 3
let circleX = this.context.width / 2
let circleY = this.context.height / 2
this.context.moveTo(circleX - radius, circleY);
this.context.arc(circleX, circleY, radius, 2 * Math.PI, 0, true);
this.context.closePath();
this.context.fillStyle = 'red';
this.context.fill();
// Draw the left eye of the smiling face.
let leftR = radius / 4
let leftX = circleX - (radius / 2)
let leftY = circleY - (radius / 3.5)
this.context.beginPath();
this.context.arc(leftX, leftY, leftR, 0, Math.PI, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
// Draw the right eye of the smiling face.
let rightR = radius / 4
let rightX = circleX + (radius / 2)
let rightY = circleY - (radius / 3.5)
this.context.beginPath();
this.context.arc(rightX, rightY, rightR, 0, Math.PI, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
// Draw the mouth of the smiling face.
let mouthR = radius / 2.5
let mouthX = circleX
let mouthY = circleY + (radius / 3)
this.context.beginPath();
this.context.arc(mouthX, mouthY, mouthR, Math.PI, 0, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
})
}
}.height('100%').width('100%')
}
}
```
The figure below shows the effect.
![WidgetCanvasDemo](figures/WidgetCanvasDemo.jpeg)
# Widget Page Capability Overview
You can leverage the ArkUI declarative paradigm to develop ArkTS widget pages. The following widget pages are automatically generated by a DevEco Studio template. You can adjust the pages based on the real-world service scenarios.
![WidgetPreviewPage](figures/WidgetPreviewPage.png)
ArkTS widgets have full capabilities of JS widgets, with added animation and custom drawing capabilities plus partial support for components, events, animations, data management, and state management capabilities of the [declarative paradigm](../reference/arkui-ts/ts-components-summary.md). For details, see [Page Capabilities Supported by ArkTS Widgets](#page-capabilities-supported-by-arkts-widgets).
## Page Capabilities Supported by ArkTS Widgets
For details about the page capabilities supported by ArkTS widgets, see [Learning ArkTS](../quick-start/arkts-create-custom-components.md) and [ArkTS-based Declarative Development Paradigm](../reference/arkui-ts/ts-components-summary.md).
Only the APIs marked with "supported in ArkTS widgets" can be used for ArkTS widgets. Pay special attention to the capability differences with applications.
For example, the following description indicates that the @Component decorator can be used in ArkTS widgets.
![WidgetSupportApi](figures/WidgetSupportApi.png)
# Updating Widget Content by State
Multiple widgets of the same application can be configured to implement different features. For example, two weather widgets can be added to the home screen: one for displaying the weather of London, and the other Beijing. The widget is set to be updated at 07:00 every morning. It needs to detect the configured city, and then updates the city-specific weather information. The following example describes how to dynamically update the widget content based on the state.
- Widget configuration file: Configure the widget to be updated at 07:00 every morning.
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,"scheduledUpdateTime": "07:00",
"updateDuration": 0,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- Widget page: A widget has different states and needs to be updated by state. When the state changes, **postCardAction** is called to notify the EntryFormAbility.
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('textA') textA: string = 'To be updated...';
@LocalStorageProp('textB') textB: string ='To be updated...';
@State selectA: boolean = false;
@State selectB: boolean = false;
build() {
Column() {
Row() {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.select(false)
.onChange((value: boolean) => {
this.selectA = value;
postCardAction(this, {
'action': 'message',
'params': {
'selectA': JSON.stringify(value)
}
});
})
Text ('State A')
}
Row() {
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.select(false)
.onChange((value: boolean) => {
this.selectB = value;
postCardAction(this, {
'action': 'message',
'params': {
'selectB': JSON.stringify(value)
}
});
})
Text ('State B')
}
Row() {// Content that is updated only in state A
Text('State A: ')
Text(this.textA)
}
Row() { // Content that is updated only in state B
Text ('State B:')
Text(this.textB)
}
}.padding('10%')
}
}
```
- EntryFormAbility: The widget state data is stored in the local database. When the update event callback is triggered, the current widget state is obtained through **formId**, and then content is updated based on the state obtained.
```ts
import formInfo from '@ohos.app.form.formInfo'
import formProvider from '@ohos.app.form.formProvider';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import dataStorage from '@ohos.data.storage'
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want) {
let formId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let isTempCard: boolean = want.parameters[formInfo.FormParam.TEMPORARY_KEY];
if (isTempCard === false) {// If the widget is a normal one, the widget information is persisted.
console.info('Not temp card, init db for:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.putSync('A' + formId, 'false');
storeDB.putSync('B' + formId, 'false');
storeDB.flushSync();
}
let formData = {};
return formBindingData.createFormBindingData(formData);
}
onRemoveForm(formId) {
console.info('onRemoveForm, formId:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.deleteSync('A' + formId);
storeDB.deleteSync('B' + formId);
}
// If the widget is a temporary one, it is recommended that the widget information be persisted when the widget is converted to a normal one.
onCastToNormalForm(formId) {
console.info('onCastToNormalForm, formId:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.putSync('A' + formId, 'false');
storeDB.putSync('B' + formId, 'false');
storeDB.flushSync();
}
onUpdateForm(formId) {
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
let stateA = storeDB.getSync('A' + formId, 'false').toString()
let stateB = storeDB.getSync('B' + formId, 'false').toString()
// Update textA in state A.
if (stateA === 'true') {
let formInfo = formBindingData.createFormBindingData({
'textA': 'AAA'
})
formProvider.updateForm(formId, formInfo)
}
// Update textB in state B.
if (stateB === 'true') {
let formInfo = formBindingData.createFormBindingData({
'textB': 'BBB'
})
formProvider.updateForm(formId, formInfo)
}
}
onFormEvent(formId, message) {
// Store the widget state.
console.info('onFormEvent formId:' + formId + 'msg:' + message);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
let msg = JSON.parse(message)
if (msg.selectA != undefined) {
console.info('onFormEvent selectA info:' + msg.selectA);
storeDB.putSync('A' + formId, msg.selectA);
}
if (msg.selectB != undefined) {
console.info('onFormEvent selectB info:' + msg.selectB);
storeDB.putSync('B' + formId, msg.selectB);
}
storeDB.flushSync();
}
};
```
> **NOTE**
> When the local database is used for widget information persistence, it is recommended that [TEMPORARY_KEY](../reference/apis/js-apis-app-form-formInfo.md#formparam) be used to determine whether the currently added widget is a normal one in the [onAddForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onaddform) lifecycle callback. If the widget is a normal one, the widget information is directly persisted. If the widget is a temporary one, the widget information is persisted when the widget is converted to a normal one ([onCastToNormalForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#oncasttonormalform)). In addition, the persistent widget information needs to be deleted when the widget is destroyed ([onRemoveForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onremoveform)), preventing the database size from continuously increasing due to repeated widget addition and deletion.
# Configuring a Widget to Update Periodically
Before configuring a widget to update periodically, enable the periodic update feature by setting the **updateEnabled** field to **true** in the **form_config.json** file.
The widget framework provides the following modes of updating widgets periodically:
- Set the update interval: The widget will be updated at the specified interval. You can specify the interval by setting the [updateDuration](arkts-ui-widget-configuration.md) field in the **form_config.json** file. For example, you can configure the widget to update once an hour.
> **NOTE**
>
> **updateDuration** takes precedence over **scheduledUpdateTime**. If both are specified, the value specified by **updateDuration** is used.
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true, // Enable the periodic update feature.
"scheduledUpdateTime": "10:30",
"updateDuration": 2, // Set the interval to update the widget. The value is a natural number, in the unit of 30 minutes.
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- Set the scheduled update time: The widget will be updated at the scheduled time every day. You can specify the time by setting the [scheduledUpdateTime](arkts-ui-widget-configuration.md) field in the **form_config.json** file. For example, you can configure the widget to update at 10:30 a.m. every day.
> **NOTE**
>
> **updateDuration** takes precedence over **scheduledUpdateTime**. For the **scheduledUpdateTime** settings to take effect, set **updateDuration** to **0**.
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true, // Enable the periodic update feature.
"scheduledUpdateTime": "10:30", // Set the scheduled time to update the widget.
"updateDuration": 0,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- Set the next update time: The widget will be updated next time at the specified time. You can specify the time by calling the [setFormNextRefreshTime()](../reference/apis/js-apis-app-form-formProvider.md#setformnextrefreshtime) API. The minimum update interval is 5 minutes. For example, you can configure the widget to update within 5 minutes after the API is called.
```ts
import formProvider from '@ohos.app.form.formProvider';
let formId = '123456789'; // Use the actual widget ID in real-world scenarios.
try {
// Configure the widget to update in 5 minutes.
formProvider.setFormNextRefreshTime(formId, 5, (err, data) => {
if (err) {
console.error(`Failed to setFormNextRefreshTime. Code: ${err.code}, message: ${err.message}`);
return;
} else {
console.info('Succeeded in setFormNextRefreshTimeing.');
}
});
} catch (err) {
console.error(`Failed to setFormNextRefreshTime. Code: ${err.code}, message: ${err.message}`);
}
```
When periodic update is triggered, the system calls the [onUpdateForm()](../reference/apis/js-apis-app-form-formExtensionAbility.md#onupdateform) lifecycle callback of the FormExtensionAbility. In the callback, [updateForm()](../reference/apis/js-apis-app-form-formProvider.md#updateform) can be used to update the widget by the provider. For details about how to use **onUpdateForm()**, see [Updating Widget Content Through FormExtensionAbility](arkts-ui-widget-event-formextensionability.md).
> **NOTE**
> 1. Each widget can be updated at the specified interval for a maximum of 50 times every day, including updates triggered by setting [updateDuration](arkts-ui-widget-configuration.md) or calling [setFormNextRefreshTime()](../reference/apis/js-apis-app-form-formProvider.md#setformnextrefreshtime). When the limit is reached, the widget cannot be updated in this mode again. The number of update times is reset at 00:00 every day.
>
> 2. The same timer is used for timing updates at the specified interval. Therefore, the first scheduled update of widgets may have a maximum deviation of 30 minutes. For example, the first widget A (updated every half an hour) is added at 03:20. The timer starts and triggers an update every half an hour. The second widget B (updated every half an hour) is added at 03:40. When the timer event is triggered at 03:50, widget A is updated, and widget B will be updated at 04:20 next time.
>
> 3. Updates at the specified interval and updates at the scheduled time are triggered only when the screen is on. When the screen is off, the update action is merely recorded. When the screen is on, the update action is performed.
# ArkTS Widget Working Principles
## Implementation Principles
**Figure 1** ArkTS widget implementation principles
![WidgetPrinciple](figures/WidgetPrinciple.png)
- Widget host: an application that displays the widget content and controls the widget location. Only the system application can function as a widget host.
- Widget provider: an application that provides the widget content to display and controls how widget components are laid out and how they interact with users.
- Widget Manager: a resident agent that manages widgets in the system. It provides the [formProvider](../reference/apis/js-apis-app-form-formProvider.md) and [formHost](../reference/apis/js-apis-app-form-formHost.md) APIs as well as widget management, usage, and periodic updates.
- Widget rendering service: a service that manages widget rendering instances. Widget rendering instances are bound to the [widget components](../reference/arkui-ts/ts-basic-components-formcomponent.md) on the widget host on a one-to-one basis. The widget rendering service runs the widget page code **widgets.abc** for rendering, and sends the rendered data to the corresponding widget component on the widget host.
**Figure 2** Working principles of the ArkTS widget rendering service
![WidgetRender](figures/WidgetRender.png)
Unlike JS widgets, ArkTS widgets support logic code running. To avoid potential ArkTS widget issues from affecting the use of applications, the widget page code **widgets.abc** is executed by the widget rendering service, which is managed by the Widget Manager. Each widget component of a widget host corresponds to a rendering instance in the widget rendering service. Rendering instances of an application provider run in the same virtual machine operating environment, and rendering instances of different application providers run in different virtual machine operating environments. In this way, the resources and state data are isolated between widgets of different application providers. During development, pay attention to the use of the [globalThis](uiability-data-sync-with-ui.md#using-globalthis-between-uiability-and-page) object. Use one **globalThis** object for widgets by the same application provider, and different **globalThis** objects for widgets by different application providers.
## Advantages of ArkTS Widgets
As a quick entry to applications, ArkTS widgets have the following advantages over JS widgets:
- Improved development experience and efficiency, thanks to the unified development paradigm
ArkTS widgets share the same declarative UI development framework as application pages. This means that the page layouts can be directly reused in widgets, improving development experience and efficiency.
**Figure 3** Comparison of widget project structures
![WidgetProject](figures/WidgetProject.png)
- More widget features
- Animation: The ArkTS widget supports the [attribute animation](../reference/arkui-ts/ts-animatorproperty.md) and [explicit animation](../reference/arkui-ts/ts-explicit-animation.md) capabilities, which can be leveraged to deliver a more engaging experience.
- Custom drawing: The ArkTS widget allows you to draw graphics with the [Canvas](../reference/arkui-ts/ts-components-canvas-canvas.md) component to present information more vividly.
- Logic code execution: The capability to run logic code in widgets means that service logic can be self-closed in widgets, expanding the service application scenarios of widgets.
## Constraints on ArkTS Widgets
Compared with JS widgets, ArkTS widgets provide more capabilities, but they are also more prone to malicious behavior. The ArkTS widget is displayed in the widget host, which is usually the home screen. To ensure user experience and power consumption, the ArkTS widget capability is restricted as follows:
- The .so file cannot be loaded.
- The native programming language cannot be used for development.
- Only [partial](arkts-ui-widget-page-overview.md) components, events, animations, data management, state management, and API capabilities of the declarative paradigm are supported.
- The event processing of the widget is independent of that of the widget host. It is recommended that you do not use the left and right sliding components when the widget host supports left and right swipes to prevent gesture conflicts.
The following features are coming to ArkTS widgets in later versions:
- Breakpoint debugging
- import statements
- Instant preview
# Removing Sticky Common Events
## When to Use
Subscribers can receive sticky common events that have been sent. If the events are no longer forwarded, the event publisher needs to remove them. OpenHarmony provides an API for removing sticky common events.
## Available APIs
For details, see [Common Event](../reference/apis/js-apis-commonEventManager.md)
| Name| Description|
| -------- | -------- |
| removeStickyCommonEvent(event: string, callback: AsyncCallback\<void>): void | Removes a sticky common event.|
## How to Develop
1. Import the module.
```ts
import commonEventManager from '@ohos.commonEventManager';
```
2. The sticky common event to be removed must have been released by the application. For details about how to release sticky common events, see [Publishing Common Events](common-event-publish.md).
```ts
CommonEventManager.removeStickyCommonEvent("sticky_event", (err) => { // sticky_event indicates the name of the sticky common event to remove.
if (err) {
console.info(`Remove sticky event AsyncCallback failed, errCode: ${err.code}, errMes: ${err.message}`);
return;
}
console.info(`Remove sticky event AsyncCallback success`);
}
});
```
# Service Widget Overview
A service widget (also called widget) is a set of UI components that display important information or operations specific to an application. It provides users with direct access to a desired application service, without the need to open the application first. A widget usually appears as a part of the UI of another application (which currently can only be a system application, such as the home screen) and provides basic interactive features such as opening a UI page or sending a message.
## Service Widget Architecture
**Figure 1** Service widget architecture
![WidgetArchitecture](figures/WidgetArchitecture.png)
Before you get started, it would be helpful if you have a basic understanding of the following concepts:
- Widget host: an application that displays the widget content and controls the widget location. An example is the home screen in the preceding figure.
- Application icon: an application entry icon, clicking which starts the application process. The icon content does not support interactions.
- Widget: an interactive UI in various sizes. It may provide buttons to implement different functions, such as the button to [update the widget content](arkts-ui-widget-event-formextensionability.md) or [switch to an application](arkts-ui-widget-event-router.md).
- Card provider: an application that provides service widget content to be displayed. It controls the display content, display logic, and component click events triggered on a service widget.
- FormExtensionAbility: widget service logic module, which provides lifecycle callbacks invoked when a widget is created, destroyed, or updated.
- Widget page: widget UI module, which contains display and interaction information such as components, layouts, and events.
Below is the typical procedure of using the widget:
**Figure 2** Typical procedure of using the widget
![WidgetUse](figures/WidgetUse.png)
1. Touch and hold an application icon on the home screen to display the shortcut menu.
2. Touch **Service widget** to access the preview screen.
3. Touch the **Add to home** button. The widget is then added to the home screen.
## Widget UI Development Mode
In the stage model, the UI of a widget can be developed in [ArkTS](arkts-ui-widget-working-principles.md) or [JS](js-ui-widget-development.md).
- A widget developed in the ArkTS-based declarative development paradigm is called ArkTS widget.
- A widget developed in the JS-compatible web-like development paradigm is called JS widget.
ArkTS widgets and JS widgets have different implementation principles and features. The following table lists the differences in capabilities.
| Category| JS widget| ArkTS widget|
| -------- | -------- | -------- |
| Development paradigm| Web-like paradigm| Declarative paradigm|
| Component capability| Supported| Supported|
| Layout capability| Supported| Supported|
| Event capability| Supported| Supported|
| Custom animation| Not supported| Supported|
| Custom drawing| Not supported| Supported|
| Logic code execution (excluding the import capability)| Not supported| Supported|
As can be seen above, ArkTS widgets have more capabilities and use cases than JS widgets. Therefore, ArkTS widgets are always recommended, except for the case where the widget consists of only static pages.
# Notification
- [Notification Overview](notification-overview.md)
- [Notification Subscription (for System Applications)](notification-subscription.md)
- [Notification Subscription (for System Applications Only)](notification-subscription.md)
- [Enabling Notification](notification-enable.md)
- [Notification Badge](notification-badge.md)
- Publishing a Notification
- [Publishing a Basic Notification](text-notification.md)
- [Publishing a Progress Notification](progress-bar-notification.md)
......
# Notification Badge
OpenHarmony provides APIs for setting the notification badge, which is displayed in the upper right corner of the application icon on the home screen to notify the user of the count of unread notifications.
When a new notification arrives, the count on the badge is incremented by 1.
After a notification is read, the count on the badge is decremented by 1. If there is no unread notification, the badge is not displayed.
## Available APIs
1. The notification service provides two methods to increase the count on the notification badge:
- When publishing a notification, pass the **badgeNumber** parameter in [NotificationRequest](../reference/apis/js-apis-notificationManager.md#notificationrequest). After the notification is received, the count on the badge is incremented.
- Call the [setBadgeNumber](../reference/apis/js-apis-notificationManager.md#setbadgenumber) API to set the count on the badge.
2. To decrease the count on the badge, call the **setBadgeNumber** API.
| API| Description|
| -------- | -------- |
| setBadgeNumber(badgeNumber: number, callback: AsyncCallback\<void\>): void | Sets the count on the badge.|
## How to Develop
1. Import the **NotificationManager** module.
```ts
import notificationManager from '@ohos.notificationManager';
```
2. Increase the count on the badge.
When publishing a notification, pass the **badgeNumber** parameter in [NotificationRequest](../reference/apis/js-apis-notificationManager.md#notificationrequest). For details, see [Publishing a Basic Notification](text-notification.md).
In this example, the **setBadgeNumber** API is called to add a badge. This API is called after a new notification is published.
```ts
function setBadgeNumberCallback(err) {
if (err) {
console.info(`Set badge failed code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Set badge success`);
}
}
let badgeNumber = 10
notificationManager.setBadgeNumber(badgeNumber, setBadgeNumberCallback);
```
3. Reduce the count on the badge.
After a notification is read, the application needs to call the API to set the number of remaining unread notifications. The badge is then updated.
```ts
function setBadgeNumberCallback(err) {
if (err) {
console.info(`Set badge failed code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Set badge success`);
}
}
let badgeNumber = 9
notificationManager.setBadgeNumber(badgeNumber, setBadgeNumberCallback);
```
......@@ -25,6 +25,9 @@
- HSP
- [In-Application HSP Development](in-app-hsp.md)
- [Inter-Application HSP Development (for System Applications Only)](cross-app-hsp.md)
- Atomic Service
- [Atomic Service Development](atomicService.md)
- [Atomic Service Space Management (for System Applications Only)](atomicService-aging.md)
- Quick Fix
- [Quick Fix Overview](quickfix-principles.md)
- [CLI-based Quick Fix Development](quickfix-debug.md)
......@@ -40,12 +43,37 @@
- [Resource Categories and Access](resource-categories-and-access.md)
- Learning ArkTS
- [Getting Started with ArkTS](arkts-get-started.md)
- ArkTS Syntax (Declarative UI)
- [Basic UI Description](arkts-basic-ui-description.md)
- State Management
- [Basic Concepts](arkts-state-mgmt-concepts.md)
- [State Management with Page-level Variables](arkts-state-mgmt-page-level.md)
- [State Management with Application-level Variables](arkts-state-mgmt-application-level.md)
- [Dynamic UI Element Building](arkts-dynamic-ui-elememt-building.md)
- [Rendering Control](arkts-rendering-control.md)
- [Restrictions and Extensions](arkts-restrictions-and-extensions.md)
\ No newline at end of file
- Basic Syntax
- [Basic Syntax Overview](arkts-basic-syntax-overview.md)
- [Declarative UI Description](arkts-declarative-ui-description.md)
- Custom Component
- [Creating a Custom Component](arkts-create-custom-components.md)
- [Page and Custom Component Lifecycle](arkts-page-custom-components-lifecycle.md)
- [\@Builder: Custom Builder Function](arkts-builder.md)
- [\@BuilderParam: @Builder Function Reference](arkts-builderparam.md)
- [\@Styles: Definition of Resusable Styles](arkts-style.md)
- [\@Extend: Extension of Built-in Components](arkts-extend.md)
- [stateStyles: Polymorphic Style](arkts-statestyles.md)
- State Management
- [State Management Overview](arkts-state-management-overview.md)
- Component State Management
- [\@State: State Owned by Component](arkts-state.md)
- [\@Prop: One-Way Synchronization from Parent to Child Components](arkts-prop.md)
- [\@Link: Two-Way Synchronization Between Parent and Child Components](arkts-link.md)
- [\@Provide and \@Consume: Two-Way Synchronization with Descendant Components](arkts-provide-and-consume.md)
- [\@Observed and \@ObjectLink: Observing Attribute Changes in Nested Class Objects](arkts-observed-and-objectlink.md)
- Application State Management
- [Application State Management Overview](arkts-application-state-management-overview.md)
- [LocalStorage: UI State Storage](arkts-localstorage.md)
- [AppStorage: Application-wide UI State Storage](arkts-appstorage.md)
- [PersistentStorage: Application State Persistence](arkts-persiststorage.md)
- [Environment: Device Environment Query](arkts-environment.md)
- Other State Management Features
- [Overview of Other State Management Features](arkts-other-state-mgmt-functions-overview.md)
- [\@Watch: Getting Notified of State Variable Changes](arkts-watch.md)
- [$$ Syntax: Two-Way Synchronization of Built-in Components](arkts-two-way-sync.md)
- Rendering Control
- [Rendering Control Overview](arkts-rendering-control-overview.md)
- [if/else: Conditional Rendering](arkts-rendering-control-ifelse.md)
- [ForEach: Rendering of Repeated Content](arkts-rendering-control-foreach.md)
- [LazyForEach: Lazy Data Loading](arkts-rendering-control-lazyforeach.md)
# Application State Management Overview
The decorators described in the previous topics are used to share state variables within a page, that is, within a component tree. If you want to share state data at the application level or across multiple pages, you would need to apply application-level state management. ArkTS provides a wide variety of application state management capabilities:
- [LocalStorage](arkts-localstorage.md): API for storing the UI state, usually used for state sharing within a [UIAbility](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis/js-apis-app-ability-uiAbility.md) or between pages.
- [AppStorage](arkts-appstorage.md): special, singleton LocalStorage object within the application, which is created by the UI framework at application startup and provides the central storage for application UI state attributes.
- [PersistentStorage](arkts-persiststorage.md): API for persisting application attributes. It is usually used together with AppStorage to persist selected AppStorage attributes to the disk so that their values are the same upon application re-start as they were when the application was closed.
- [Environment](arkts-environment.md): a range of environment parameters regarding the device where the application runs. The environment parameters are synchronized to the AppStorage and can be used together with the AppStorage.
# AppStorage: Application-wide UI State Storage
AppStorage provides the central storage for mutable application UI state attributes. It is bound to the application process and is created by the UI framework at application startup.
Unlike LocalStorage, which is usually used for page-level state sharing, AppStorage enables application-wide UI state sharing. AppStorage is equivalent to the hub of the entire application. [PersistentStorage](arkts-persiststorage.md) and [Environment](arkts-environment.md) data is passed first to AppStorage and then from AppStorage to the UI component.
This topic describes only the AppStorage application scenarios and related decorators: \@StorageProp and \@StorageLink.
## Overview
AppStorage is a singleton LocalStorage object that is created by the UI framework at application startup. Its purpose is to provide the central storage for mutable application UI state attributes. AppStorage retains all those attributes and their values as long as the application remains running. Attributes are accessed using a unique key string value.
UI components synchronize application state attributes with the AppStorage. Implementation of application business logic can access AppStorage as well.
Selected state attributes of AppStorage can be synched with different data sources or data sinks. Those data sources and sinks can be on a local or remote device, and have different capabilities, such as data persistence (see [PersistentStorage](arkts-persiststorage.md)). These data sources and sinks are implemented in the business logic, separate from the UI. Link those AppStorage attributes to [@StorageProp](#storageprop) and [@StorageLink](#storagelink) whose values should be kept until application re-start.
## \@StorageProp
As mentioned above, if you want to establish a binding between AppStorage and a custom component, you need to use the \@StorageProp and \@StorageLink decorators. Use \@StorageProp(key) or \@StorageLink(key) to decorate variables in the component. **key** identifies the attribute in AppStorage.
When a custom component is initialized, the \@StorageProp(key)/\@StorageLink(key) decorated variable is initialized with the value of the attribute with the given key in AppStorage. Local initialization is mandatory. If an attribute with the given key is missing from AppStorage, it will be added with the stated initializing value. (Whether the attribute with the given key exists in AppStorage depends on the application logic.)
By decorating a variable with \@StorageProp(key), a one-way data synchronization is established with the attribute with the given key in AppStorage. A local change can be made, but it will not be synchronized to AppStorage. An update to the attribute with the given key in AppStorage will overwrite local changes.
### Rules of Use
| \@StorageProp Decorator| Description |
| ------------------ | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | One-way: from the attribute in AppStorage to the component variable.<br>The component variable can be changed locally, but an update from AppStorage will overwrite local changes.|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in AppStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize a n \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 1** \@StorageProp initialization rule
![en-us_image_0000001552978157](figures/en-us_image_0000001552978157.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
- When the value change of the \@StorageProp(key) decorated variable is observed, the change is not synchronized to the attribute with the give key value in AppStorage.
- The value change of the \@StorageProp(key) decorated variable only applies to the private member variables of the current component, but not other variables bound to the key.
- When the data decorated by \@StorageProp(key) is a state variable, the change of the data is not synchronized to AppStorage, but the owning custom component is re-rendered.
- When the attribute with the given key in AppStorage is updated, the change is synchronized to all the \@StorageProp(key) decorated data, and the local changes of the data are overwritten.
## \@StorageLink
\@StorageLink(key) creates a two-way data synchronization with the attribute with the given key in AppStorage.
1. If a local change occurs, it is synchronized to AppStorage.
2. Changes in AppStorage are synchronized to all attributes with the given key, including one-way bound variables (\@StorageProp decorated variables and one-way bound variables created through \@Prop), two-way bound variables (\@StorageLink decorated variables and two-way bound variables created through \@Link), and other instances (such as PersistentStorage).
### Rules of Use
| \@StorageLink Decorator| Description |
| ------------------ | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in AppStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | Two-way: from the attribute in AppStorage to the custom component variable and back|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in AppStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden. |
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 2** \@StorageLink initialization rule
![en-us_image_0000001501938718](figures/en-us_image_0000001501938718.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
1. When the value change of the \@StorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the give key value in AppStorage.
2. Once the attribute with the given key in AppStorage is updated, all the data (including \@StorageLink and \@StorageProp decorated variables) bound to the attribute key is changed synchronously.
3. When the data decorated by \@StorageLink(key) is a state variable, the change of the data is synchronized to AppStorage, and the owning custom component is re-rendered.
## Application Scenarios
### Example of Using AppStorage and LocalStorage from Application Logic
Since AppStorage is a singleton, its APIs are all static ones. How these APIs work resembles the non-static APIs of LocalStorage.
```ts
AppStorage.SetOrCreate('PropA', 47);
let storage: LocalStorage = new LocalStorage({ 'PropA': 17 });
let propA: number = AppStorage.Get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
var link1: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link1.get() == 47
var link2: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link2.get() == 47
var prop: SubscribedAbstractProperty<number> = AppStorage.Prop('PropA'); // prop.get() = 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get()=1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
storage.get('PropA') // == 17
storage.set('PropA', 101);
storage.get('PropA') // == 101
AppStorage.Get('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
```
### Example of Using AppStorage and LocalStorage from Inside the UI
\@StorageLink works together with the AppStorage in the same way as \@LocalStorageLink works together with LocalStorage. It creates two-way data synchronization with an attribute in AppStorage.
```ts
AppStorage.SetOrCreate('PropA', 47);
let storage = new LocalStorage({ 'PropA': 48 });
@Entry(storage)
@Component
struct CompA {
@StorageLink('PropA') storLink: number = 1;
@LocalStorageLink('PropA') localStorLink: number = 1;
build() {
Column({ space: 20 }) {
Text(`From AppStorage ${this.storLink}`)
.onClick(() => this.storLink += 1)
Text(`From LocalStorage ${this.localStorLink}`)
.onClick(() => this.localStorLink += 1)
}
}
}
```
## Restrictions
When using AppStorage together with [PersistentStorage](arkts-persiststorage.md) and [Environment](arkts-environment.md), pay attention to the following:
- A call to **PersistentStorage.PersistProp()** after creating the attribute in AppStorage uses the type and value in AppStorage and overwrites any attribute with the same name in PersistentStorage. In light of this, the opposite order of calls is recommended. For an example of incorrect usage, see [Accessing Attribute in AppStorage Before PersistentStorage](arkts-persiststorage.md#accessing-attribute-in-appstorage-before-persistentstorage).
- A call to **Environment.EnvProp()** after creating the attribute in AppStorage will fail. This is because AppStorage already has an attribute with the same name, and the environment variable will not be written into AppStorage. Therefore, you are advised not to use the preset environment variable name in AppStorage.
<!--no_check-->
# Basic Syntax Overview
With a basic understanding of the ArkTS language, let's look into the basic composition of ArkTS through an example. As shown below, when the user clicks the button, the text content changes from **Hello World** to **Hello ArkUI**.
**Figure 1** Example effect drawing
![Video_2023-03-06_152548](figures/Video_2023-03-06_152548.gif)
In this example, the basic composition of ArkTS is as follows.
**Figure 2** Basic composition of ArkTS
![arkts-basic-grammar](figures/arkts-basic-grammar.png)
- Decorator: design pattern used to decorate classes, structures, methods, and variables to assign special meanings to them. In the preceding sample code, \@Entry, \@Component, and \@State are decorators. \@Component indicates a custom component, \@Entry indicates that the custom component is an entry component, and \@State indicates a state variable in the component, whose change will trigger the UI to re-render.
- [UI description](arkts-declarative-ui-description.md): declarative description of the UI structure, such as the code block of the **build()** method.
- [Custom component](arkts-create-custom-components.md): reusable UI unit, which can be combined with other components, such as the struct **Hello** decorated by @Component.
- Built-in component: default basic or container component preset in ArkTS, which can be directly invoked, such as** \<Column>**,** \<Text>**, **\<Divider>**, and **\<Button>** components in the sample code.
- Attribute method: method used to configure component attributes, such as **fontSize()**, **width()**, **height()**, and **color()**. You can configure multiple attributes of a component in method chaining mode.
- Event method: method used to add the logic for a component to respond to an event. In the sample code, **onClick()** following **Button** is an event method. You can configure response logic for multiple events in method chaining mode.
ArkTS extends multiple syntax paradigms to make development a more enjoyable experience.
- [@Builder](arkts-builder.md)/[@BuilderParam](arkts-builderparam.md): special method for encapsulating UI descriptions. It enables UI descriptions to be encapsulated and reused in a fine-grained manner.
- [@Extend](arkts-extend.md)/[@Style](arkts-style.md): decorator that extends built-in components and encapsulates attribute styles to combine built-in components more flexibly.
- [stateStyles](arkts-statestyles.md): polymorphic style, which can be set based on the internal state of the component.
# Basic UI Description
In ArkTS, you define a custom component by using decorators **@Component** and **@Entry** to decorate a data structure declared with the **struct** keyword. A custom component provides a **build** function, where you must write the basic UI description in chain call mode. For details about the UI description, see [UI Description Specifications](#ui-description-specifications).
## Basic Concepts
- struct: a data structure that can be used to implement custom components and cannot have inheritance. The **new** keyword can be omitted when initializing a struct.
- Decorator: a special type of declaration that can be applied to classes, structures, or class attributes to add new functionality to them. Multiple decorators can be applied to the same target element and defined on a single line or multiple lines. It is recommended that the decorators be defined on multiple lines.
```ts
@Entry
@Component
struct MyComponent {
}
```
- **build** function: A custom component must implement the **build** function and must implement no constructor. The **build** function meets the definition of the **Builder** API and is used to define the declarative UI description of components.
```ts
interface Builder {
build: () => void
}
```
- **@Component**: a decorator applied to a struct to equip it with the component-based capability. The **build** method must be implemented for UI creation.
- **@Entry**: a decorator applied to a struct to make it the entry to a page, which is rendered and displayed when the page is loaded.
- **@Preview**: a decorator applied to struct to make it previewable in the DevEco Studio Previewer. The decorated component is created and displayed when the residing page is loaded.
> **NOTE**
>
> In a single source file, you can use up to 10 **@Preview** decorators to decorate custom components. For details, see [Previewing ArkTS Components](https://developer.harmonyos.com/en/docs/documentation/doc-guides/ohos-previewing-app-service-0000001218760596#section146052489820).
- Chain call: a syntax for configuring the attribute methods, event methods, and more of UI components by using the dot notation.
## UI Description Specifications
### Structs Without Parameters
A struct without parameters is a component whose API definition has empty parentheses. No parameter needs to be passed to this type of component, for example, the **Divider** component in the following snippet:
```ts
Column() {
Text('item 1')
Divider()
Text('item 2')
}
```
### Structs with Parameters
A struct with parameters is a component whose API definition expects parameters enclosed in the parentheses. You can use constants to assign values to the parameters.
Sample code:
- Set the mandatory parameter **src** of the **\<Image>** component as follows:
```ts
Image('https://xyz/test.jpg')
```
- Set the optional parameter **content** of the **\<Text>** component as follows:
```ts
Text('test')
```
You can use variables or expressions to assign values to parameters. The result type returned by an expression must meet the parameter type requirements. For details about the variables, see [State Management with Page-level Variables](arkts-state-mgmt-page-level.md) and [State Management with Application-level Variables](arkts-state-mgmt-application-level.md). For example, set a variable or expression to construct the **\<Image>** and **\<Text>** components:
```ts
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
```
### Attribute Configuration
Component attributes are configured using an attribute method, which follows the corresponding component and is bound to the component using the "**.**" operator.
- Example of configuring the font size attribute of the **\<Text>** component:
```ts
Text('test')
.fontSize(12)
```
- Example of configuring multiple attributes at the same time by using the "**.**" operator to implement chain call:
```ts
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
```
- Example of passing variables or expressions in addition to constants:
```ts
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
```
- For attributes of built-in components, ArkUI also provides some predefined [enumeration types](../reference/arkui-ts/ts-appendix-enums.md), which you can pass as parameters to methods if they meet the parameter type requirements. For example, you can configure the font color and weight attributes of the **\<Text>** component as follows:
```ts
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
```
### Event Configuration
Events supported by components are configured using event methods, which each follow the corresponding component and are bound to the component using the "**.**" operator.
- Example of using a lambda expression to configure the event of a component:
```ts
Button('add counter')
.onClick(() => {
this.counter += 2;
})
```
- Example of using an anonymous function expression to configure the event of a component (**bind** must be used to ensure that the contained components are referenced by **this** in the function body):
```ts
Button('add counter')
.onClick(function () {
this.counter += 2;
}.bind(this))
```
- Example of using a component's member function to configure the event of the component:
```ts
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
```
### Child Component Configuration
For a component that supports child components, for example, a container component, add the UI descriptions of the child components inside parentheses. The **\<Column>**, **\<Row>**, **\<Stack>**, **\<Grid>**, and **\<List>** components are all container components.
- Simple example of the **\<Column>** component:
```ts
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
```
- Example of nesting multiple child components in the **\<Column>** component:
```ts
Column() {
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
Divider()
Row() {
Image('test2.jpg')
.width(100)
.height(100)
Button('click +2')
.onClick(() => {
console.info('+2 clicked!');
})
}
Divider()
Row() {
Image('test3.jpg')
.width(100)
.height(100)
Button('click +3')
.onClick(() => {
console.info('+3 clicked!');
})
}
}
```
# \@Builder: Custom Builder Function
After a custom component is created, its internal UI structure is fixed and allows only data passing with its caller. ArkUI also provides a more lightweight mechanism for reusing UI elements: \@Builder. An \@Builder decorated function is a special function that serves similar purpose as the build function. The \@Builder function body follows the same syntax rules as the **build** function. You can abstract reusable UI elements into a method and call the method in **build**.
To simplify language, here we refer to an \@Builder decorated function also as a custom builder function.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Custom Builder Function
Syntax:
```ts
@Builder myBuilderFunction({ ... })
```
Usage:
```ts
this.myBuilderFunction({ ... })
```
- Defining one or more custom builder (\@Builder decorated) functions inside a custom component is allowed. Such a custom builder function can be considered as a private, special type of member functions of that component.
- The custom builder function can be called from the owning component's** build** or another custom builder (within that custom component) function only.
- Inside the custom builder function body, **this** refers to the owning component. Component state variables are accessible from within the custom builder function implementation. Using the custom components' state variables is recommended over parameter passing.
### Global Custom Builder Function
Syntax:
```ts
@Builder function MyGlobalBuilderFunction({ ... })
```
Usage:
```ts
MyGlobalBuilderFunction()
```
- A global custom builder function is accessible from the entire application. **this** and the **bind** method are not allowed.
- Use of a global custom builder function is recommended if no own state is required.
## Parameter Passing Rules
There are two types of parameter passing for custom builder functions: [by-value parameter passing](#by-value-parameter-passing) and [by-reference parameter passing](#by-reference-parameter-passing). Both of them must comply with the following rules:
- The parameter type must be the same as the declared parameter type. The **undefined** or **null** constants as well as expressions evaluating to these values are not allowed.
- All parameters are immutable inside the custom builder function. If mutability and synchronization of the mutation is required, the custom builder should be replaced by a custom component with a [@Link](arkts-link.md) decorated variable.
- The \@Builder function body follows the same [syntax rules](arkts-create-custom-components.md#build-function) as the **build** function.
### By-Reference Parameter Passing
In by-reference parameter passing, the passed parameters can be state variables, and the change of these state variables causes the UI re-rendering in the \@Builder method. ArkUI provides $$ as a paradigm for by-reference parameter passing.
```ts
ABuilder( $$ : { paramA1: string, paramB1 : string } );
```
```ts
@Builder function ABuilder($$: { paramA1: string }) {
Row() {
Text(`UseStateVarByReference: ${$$.paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
// Pass the this.label reference to the ABuilder component when the ABuilder component is called in the Parent component.
ABuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
// After Click me is clicked, the UI text changes from Hello to ArkUI.
this.label = 'ArkUI';
})
}
}
}
```
### By-Value Parameter Passing
By default, parameters in the \@Builder decorated functions are passed by value. When the passed parameter is a state variable, the change of the state variable does not cause the UI re-rendering in the \@Builder method. Therefore, when using state variables, you are advised to use [by-reference parameter passing](#by-reference-parameter-passing).
```ts
@Builder function ABuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
label: string = 'Hello';
build() {
Column() {
ABuilder(this.label)
}
}
}
```
# \@BuilderParam: @Builder Function Reference
In certain circumstances, you may need to add a specific function, such as a click-to-jump action, to a custom component. However, embedding an event method directly inside of the component will add the function to all places where the component is imported. This is where the \@BuilderParam decorator comes into the picture. \@BuilderParam is used to decorate a custom component member variable of type reference to an \@Builder method. When initializing a custom component, you can assign a value to the variable, thereby adding the specific function to the custom component. This decorator can be used to declare an element of any UI description, similar to a slot placeholder.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Initializing \@BuilderParam Decorated Method
An \@BuildParam decorated method can be initialized only by an \@Builder function reference.
- Local initialization with the owning component's custom \@Builder function or a global \@Builder function reference
```ts
@Builder function GlobalBuilder0() {}
@Component
struct Child {
@Builder doNothingBuilder() {};
@BuilderParam aBuilder0: () => void = this.doNothingBuilder;
@BuilderParam aBuilder1: () => void = GlobalBuilder0;
build(){}
}
```
- Initialization from the parent component
```ts
@Component
struct Child {
@BuilderParam aBuilder0: () => void;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
Child({ aBuilder0: this.componentBuilder })
}
}
}
```
- **this** in the function body points to the correct object.
In the following example, when the **Parent** component calls **this.componentBuilder()**, **this.label** points to the owning component, that is, **Parent**. With **\@BuilderParam aBuilder0** passed to the **Child** component from **\@Builder componentBuilder()**, when the **Child** component calls **this.aBuilder0()**, **this.label** points to the label of the **Child** component, that is, **Child**.
> **NOTE**
>
> Exercise caution when using **bind** to change the context of function invoking, which may cause **this** to point to an incorrect object.
```ts
@Component
struct Child {
label: string = `Child`
@BuilderParam aBuilder0: () => void;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
label: string = `Parent`
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder })
}
}
}
```
## Application Scenarios
### Component Initialization Through Parameters
An \@BuilderParam decorated method can be a method with or without parameters. Whether it contains parameters should match that of the assigned \@Builder method. The type of the \@BuilderParam decorated method must also match that of the assigned \@Builder method.
```ts
@Builder function GlobalBuilder1($$ : {label: string }) {
Text($$.label)
.width(400)
.height(50)
.backgroundColor(Color.Blue)
}
@Component
struct Child {
label: string = 'Child'
// Without parameters. The pointed componentBuilder is also without parameters.
@BuilderParam aBuilder0: () => void;
// With parameters. The pointed GlobalBuilder1 is also with parameters.
@BuilderParam aBuilder1: ($$ : { label : string}) => void;
build() {
Column() {
this.aBuilder0()
this.aBuilder1({label: 'global Builder label' } )
}
}
}
@Entry
@Component
struct Parent {
label: string = 'Parent'
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 })
}
}
}
```
### Example of Component Initialization Through Trailing Closure
In a custom component, the \@BuilderParam decorated attribute can be initialized using a trailing closure. During initialization, the component name is followed by a pair of braces ({}) to form a trailing closure.
> **NOTE**
> In this scenario, the custom component has one and only one \@BuilderParam decorated attribute.
You can pass the content in the trailing closure to \@BuilderParam as an \@Builder decorated method. Example:
```ts
// xxx.ets
@Component
struct CustomContainer {
@Prop header: string;
@BuilderParam closer: () => void
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// Create the CustomContainer component. During initialization, append a pair of braces ({}) to the component name to form a trailing closure.
// Used as the parameter passed to CustomContainer @BuilderParam closer: () => void.
CustomContainer({ header: this.text }) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader';
})
}
}
}
}
```
# Creating a Custom Component
In ArkUI, components are what's displayed on the UI. They can be classified as built-in components – those directly provided by ArkUI framework, and custom components – those defined by developers. Defining the entire application UI with just built-in components would lead to a monolithic design, low code maintainability, and poor execution performance. A good UI is the result of a well-thought-out development process, with such factors as code reusability, separation of service logic from the UI, and version evolution carefully considered. Creating custom components that encapsulate the UI and some business logic is a critical step in this process.
The custom component has the following features:
- Combinable: allows you to combine built-in components and other components, as well as their attributes and methods.
- Reusable: can be reused by other components and used as different instances in different parent components or containers.
- Data-driven update: holds some state and triggers UI re-rendering with the change of state variables.
The following example shows the basic usage of a custom component.
```ts
@Component
struct HelloComponent {
@State message: string = 'Hello, World!';
build() {
// The HelloComponent custom component combines the <Row> and <Text> built-in components.
Row() {
Text(this.message)
.onClick(() => {
// The change of the state variable message drives the UI to be re-rendered. As a result, the text changes from "Hello, World!" to "Hello, ArkUI!".
this.message = 'Hello, ArkUI!';
})
}
}
}
```
Multiple **HelloComponent** instances can be created in the **build()** function of other custom components. In this way, **HelloComponent** is reused by those custom components.
```ts
@Entry
@Component
struct ParentComponent {
build() {
Column() {
Text('ArkUI message')
HelloComponent({ message: 'Hello, World!' });
Divider()
HelloComponent ({ message: 'Hello!' });
}
}
}
```
To fully understand the preceding example, a knowledge of the following concepts is essential:
- [Basic Structure of a Custom Component](#basic-structure-of-a-custom-component)
- [Member Functions/Variables](#member-functionsvariables)
- [Rules of for Custom Component Parameters](#rules-of-for-custom-component-parameters)
- [build Function](#build-function)
- [Universal Style of a Custom Component](#universal-style-of-a-custom-component)
- [Custom Attribute Methods](#custom-attribute-methods)
## Basic Structure of a Custom Component
- struct: The definition of a custom component must start with the \@Component struct followed by the component name, and then component body enclosed by curly brackets {....}. No inheritance is allowed. You can omit the **new** operator when instantiating a struct.
> **NOTE**
>
> The name or its class or function name of a custom component must be different from that of any built-in components.
- \@Component: The \@Component decorator can decorate only the data structures declared by the **struct** keyword. After being decorated by \@Component, a struct has the componentization capability. It must implement the **build** function to describe the UI. One struct can be decorated by only one \@Component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
```ts
@Component
struct MyComponent {
}
```
- build(): The **build()** function is used to define the declarative UI description of a custom component. Every custom component must define a **build()** function.
```ts
@Component
struct MyComponent {
build() {
}
}
```
- \@Entry: A custom component decorated with \@Entry is used as the default entry component of the page. At most one component can be decorated with \@Entry in a single source file. The \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md).
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
```ts
@Entry
@Component
struct MyComponent {
}
```
## Member Functions/Variables
In addition to the mandatory** build()** function, a custom component may implement other member functions with the following restrictions:
- Static functions are not supported.
- Access to the member functions is always private. Defining **private** access is optional. Defining access other than **private** is a syntax error.
A custom component can also implement member variables with the following restrictions:
- Static member variables are not supported.
- Access to the member variables is always private.The access rules of member variables are the same as those of member functions.
- Local initialization is optional for some member variables and mandatory for others. For details about whether local initialization or initialization from the parent component is required, see [State Management](arkts-state-management-overview.md).
## Rules of for Custom Component Parameters
As can be learnt from preceding examples, a custom component can be created from a **build** or [@Builder](arkts-builder.md) function, and during the creation, parameters can be supplied to the component.
```ts
@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue;
build() {
}
}
@Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink;
build() {
Column() {
// Create an instance of MyComponent and initialize its countDownFrom variable with the value 10 and its color variable with the value this.someColor.
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
```
## build Function
All languages declared in the **build** function are called UI description languages. The UI description languages must comply with the following rules:
- For an \@Entry decorated custom component, exactly one root component is required under the **build** function. This root component must be a container component. **ForEach** is not allowed at the top level.
For an \@Component decorated custom component, exactly one root component is required under the **build** function. This root component is not necessarily a container component. **ForEach** is not allowed at the top level.
```ts
@Entry
@Component
struct MyComponent {
build() {
// Exactly one root component is required, and it must be a container component.
Row() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
build() {
// Exactly one root component is required, and it is not necessarily a container component.
Image('test.jpg')
}
}
```
- Local variable declaration is not allowed. The following example is invalid:
```ts
build() {
// Invalid: Local variable declaration is not allowed.
let a: number = 1;
}
```
- **console.info** cannot be directly used in the UI description, but can be used in methods or functions. The following is an example:
```ts
build() {
// Invalid: Use of console.info is not allowed.
console.info('print debug log');
}
```
- Creation of a local scope is not allowed. The following example is invalid:
```ts
build() {
// Invalid: Creation of local scope is not allowed.
{
...
}
}
```
- Calling a function other than the \@Builder decorated is not allowed. The parameters of built-in components can be the return values of TS methods.
```ts
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// Invalid: No function calls except @Builder functions.
this.doSomeCalculations();
// Valid: The function can be called.
this.doSomeRender();
// Valid: The parameter can be the return value of a TS method.
Text(this.calcTextValue())
}
}
}
```
- The **switch** syntax is not allowed. Use **if** instead. The following example is invalid:
```ts
build() {
Column() {
// Invalid: The switch syntax is not allowed.
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
}
}
```
- Expressions are not allowed. The following example is invalid:
```ts
build() {
Column() {
// Invalid: Expressions are not allowed.
(this.aVar > 10) ? Text('...') : Image('...')
}
}
```
## Universal Style of a Custom Component
The universal style of a custom component is configured by invoking chainable attribute methods.
```ts
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
```
> **NOTE**
>
> When ArkUI sets styles for custom components, an invisible container component is set for **MyComponent2**. These styles are set on the container component instead of the **\<Button>** component of **MyComponent2**. As seen from the rendering result, the red background color is not directly applied to the button. Instead, it is applied to the container component that is invisible to users where the button is located.
## Custom Attribute Methods
Custom components do not support custom attribute methods. You can use the Controller capability to implement custom APIs.
```ts
// Custom controller
export class MyComponentController {
item: MyComponent = null;
setItem(item: MyComponent) {
this.item = item;
}
changeText(value: string) {
this.item.value = value;
}
}
// Custom component
@Component
export default struct MyComponent {
public controller: MyComponentController = null;
@State value: string = 'Hello World';
build() {
Column() {
Text(this.value)
.fontSize(50)
}
}
aboutToAppear() {
if (this.controller)
this.controller.setItem (this); // Link to the controller.
}
}
// Processing logic
@Entry
@Component
struct StyleExample {
controller = new MyComponentController();
build() {
Column() {
MyComponent({ controller: this.controller })
}
.onClick(() => {
this.controller.changeText('Text');
})
}
}
```
In the preceding example:
1. The **aboutToAppear** method of the **MyComponent** child component passes the current **this** pointer to the **item** member variable of **MyComponentController**.
2. The **StyleExample** parent component holds a **Controller** instance and with which calls the **changeText** API of **Controller**. That is, the value of the state variable **value** of **MyComponent** is changed through the **this** pointer of the **MyComponent** child component held by the controller.
Through the encapsulation of the controller, **MyComponent** exposes the **changeText** API. All instances that hold the controller can call the **changeText** API to change the value of the **MyComponent** state variable **value**.
<!--no_check-->
# Declarative UI Description
ArkTS declaratively combines and extends components to describe the UI of an application. It also provides basic methods for configuring attributes, events, and child components to help you implement application interaction logic.
## Creating a Component
Depending on the builder, you can create components with or without mandatory parameters.
> **NOTE**
>
> The **new** operator is not required when you create a component.
### Without Mandatory Parameters
A struct without mandatory parameters is a component whose API definition has empty parentheses. No parameter needs to be passed to this type of component, for example, the **Divider** component in the following snippet:
```ts
Column() {
Text('item 1')
Divider()
Text('item 2')
}
```
### With Mandatory Parameters
A struct with mandatory parameters is a component whose API definition expects parameters enclosed in the parentheses.
- Set the mandatory parameter **src** of the **\<Image>** component as follows:
```ts
Image('https://xyz/test.jpg')
```
- Set the optional parameter **content** of the **\<Text>** component.
```ts
// Parameter of the string type
Text('test')
// Add application resources in $r format, which can be used in multi-language scenarios.
Text($r('app.string.title_value'))
// No mandatory parameters
Text()
```
- You can also use variables or expressions to assign values to parameters. The result type returned by an expression must meet the parameter type requirements.
For example, to set a variable or expression to construct the **\<Image>** and **\<Text>** components:
```ts
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
```
## Configuring Attributes
Use chainable attribute methods to configure the style and other attributes of built-in components. It is recommended that a separate line be used for each attribute method.
- Example of configuring the **fontSize** attribute for the **\<Text>** component:
```ts
Text('test')
.fontSize(12)
```
- Example of configuring multiple attributes for the **\<Image>** component:
```ts
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
```
- Attribute methods accept expressions and variables as well constant parameters.
```ts
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
```
- For built-in components, ArkUI also predefines some enumeration types. These enumeration types can be passed as parameters, as long as they meet the parameter type requirements.
Example of configuring the font color and style of the **\<Text>** component:
```ts
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
```
## Handling Events
Use chainable event methods to configure events supported by built-in components. It is recommended that a separate line be used for each event method.
- Example of using a lambda expression to configure the event of a component:
```ts
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
```
- Example of using an anonymous function expression to configure the event of a component (**bind** must be used to ensure that the current components are referenced by **this **in the function body):
```ts
Button('add counter')
.onClick(function(){
this.counter += 2;
}.bind(this))
```
- Example of using a component's member function to configure the event of the component:
```ts
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
```
## Configuring Child Components
For a component that supports child components, for example, a container component, add the UI descriptions of the child components inside parentheses. The **\<Column>**, **\<Row>**, **\<Stack>**, **\<Grid>**, and **\<List>** components are all container components.
- Simple example of configuring child components for the **\<Column>** component:
```ts
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
```
- Example of nesting multiple child components in the **\<Column>** component:.
```ts
Column() {
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
}
```
# Dynamic UI Element Building
After you've created a custom component (as described in [Basic UI Description](arkts-basic-ui-description.md)), you can customize the internal UI structure for the component, by drawing on the capability of dynamic UI element building.
## @Builder
The **@Builder** decorator is used to decorate a function for quickly generating multiple layouts in a custom component. This function can be declared outside the **build** function and used in the **build** function or other **@Builder** decorated functions. The following example shows how to use **@Builder**.
```ts
// xxx.ets
@Component
struct CompB {
@State CompValue: string = ''
aboutToAppear() {
console.info('CompB aboutToAppear.')
}
aboutToDisappear() {
console.info('CompB aboutToDisappear.')
}
build() {
Column() {
Button(this.CompValue)
.margin(5)
}
}
}
@Entry
@Component
struct CompA {
size1: number = 100
@State CompValue1: string = "Hello,CompValue1"
@State CompValue2: string = "Hello,CompValue2"
@State CompValue3: string = "Hello,CompValue3"
// Use the custom component CompB in the @Builder decorated function CompC.
@Builder CompC(value: string) {
CompB({ CompValue: value })
}
@Builder SquareText(label: string) {
Text(label)
.fontSize(18)
.width(1 * this.size1)
.height(1 * this.size1)
}
// Use the @Builder decorated function SquareText in the @Builder decorated function RowOfSquareTexts.
@Builder RowOfSquareTexts(label1: string, label2: string) {
Row() {
this.SquareText(label1)
this.SquareText(label2)
}
.width(2 * this.size1)
.height(1 * this.size1)
}
build() {
Column() {
Row() {
this.SquareText("A")
this.SquareText("B")
}
.width(2 * this.size1)
.height(1 * this.size1)
this.RowOfSquareTexts("C", "D")
Column() {
// Use the @Builder decorated custom components three times.
this.CompC(this.CompValue1)
this.CompC(this.CompValue2)
this.CompC(this.CompValue3)
}
.width(2 * this.size1)
.height(2 * this.size1)
}
.width(2 * this.size1)
.height(2 * this.size1)
}
}
```
![builder](figures/builder.PNG)
## @BuilderParam<sup>8+</sup>
The **@BuilderParam** decorator is used to decorate the function type attributes (for example, **@BuilderParam noParam: () => void**) in a custom component. When the custom component is initialized, the attributes decorated by **@BuilderParam** must be assigned values.
### Background
In certain circumstances, you may need to add a specific function, such as a click-to-jump action, to a custom component. However, embedding an event method directly inside of the component will add the function to all places where the component is imported. This is where the **@BuilderParam** decorator comes into the picture. When initializing a custom component, you can assign a **@Builder** decorated method to the **@BuilderParam** decorated attribute, thereby adding the specific function to the custom component.
### Component Initialization Through Parameters
When initializing a custom component through parameters, assign a **@Builder** decorated method to the **@BuilderParam** decorated attribute — **content**, and call the value of **content** in the custom component. If no parameter is passed when assigning a value to the **@BuilderParam** decorated attribute (for example, **noParam: this.specificNoParam**), define the type of the attribute as a function without a return value (for example, **@BuilderParam noParam: () => void**). If any parameter is passed when assigning a value to the **@BuilderParam** decorated attribute (for example, **withParam: this.SpecificWithParam('WithParamA')**), define the type of the attribute as **any** (for example, **@BuilderParam withParam: any**).
```ts
// xxx.ets
@Component
struct CustomContainer {
header: string = ''
@BuilderParam noParam: () => void
@BuilderParam withParam: any
footer: string = ''
build() {
Column() {
Text(this.header)
.fontSize(30)
this.noParam()
this.withParam()
Text(this.footer)
.fontSize(30)
}
}
}
@Entry
@Component
struct CustomContainerUser {
@Builder specificNoParam() {
Column() {
Text('noParam').fontSize(30)
}
}
@Builder SpecificWithParam(label: string) {
Column() {
Text(label).fontSize(30)
}
}
build() {
Column() {
CustomContainer({
header: 'HeaderA',
noParam: this.specificNoParam,
withParam: this.SpecificWithParam('WithParamA'),
footer: 'FooterA'
})
Divider()
.strokeWidth(3)
.margin(10)
CustomContainer({
header: 'HeaderB',
noParam: this.specificNoParam,
withParam: this.SpecificWithParam('WithParamB'),
footer: 'FooterB'
})
}
}
}
```
![builder1](figures/builder1.PNG)
### Component Initialization Through Trailing Closure
In a custom component, the **@BuilderParam** decorated attribute can be initialized using a trailing closure. During initialization, the component name is followed by a pair of braces ({}) to form a trailing closure (**CustomContainer(){}**). You can consider a trailing closure as a container and add content to it. For example, you can add a component (**{Column(){...}**) to the closure. The syntax of the closure is the same as that of **build**. In this scenario, the custom component has one and only one **@BuilderParam** decorated attribute.
Example: Add a **\<Column>** component and a click event to the closure, and call the **specificParam** method decorated by **@Builder** in the new **\<Column>** component. After the **\<Column>** component is clicked, the value of the **CustomContainer** component's **header** attribute will change from **header** to **changeHeader**. When the component is initialized, the content of the trailing closure will be assigned to the **closer** attribute decorated by **@BuilderParam**.
```ts
// xxx.ets
@Component
struct CustomContainer {
header: string = ''
@BuilderParam closer: () => void
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header'
build() {
Column() {
CustomContainer({
header: this.text,
}) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader'
})
}
}
}
}
```
![builder2](figures/builder2.gif)
## @Styles
The **@Styles** decorator helps avoid repeated style setting, by extracting multiple style settings into one method. When declaring a component, you can invoke this method and use the **@Styles** decorator to quickly define and reuse the custom styles of a component. **@Styles** supports only universal attributes.
**@Styles** can be defined inside or outside a component declaration. When it is defined outside a component declaration, the component name must be preceded by the keyword **function**.
```ts
// xxx.ets
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@Styles componentFancy() {
.width(100)
.height(200)
.backgroundColor(Color.Yellow)
}
build() {
Column({ space: 10 }) {
Text('FancyA')
.globalFancy()
.fontSize(30)
Text('FancyB')
.globalFancy()
.fontSize(20)
Text('FancyC')
.componentFancy()
.fontSize(30)
Text('FancyD')
.componentFancy()
.fontSize(20)
}
}
}
```
![styles](figures/styles.PNG)
**@Styles** can also be used inside the **[StateStyles](../reference/arkui-ts/ts-universal-attributes-polymorphic-style.md)** attribute declaration of a component, to assign state-specific attributes to the component.
In **StateStyles**, **@Styles** decorated methods defined outside the component can be directly called, while those defined inside can be called only with the keyword **this**.
```ts
// xxx.ets
@Styles function globalFancy () {
.width(120)
.height(120)
.backgroundColor(Color.Green)
}
@Entry
@Component
struct FancyUse {
@Styles componentFancy() {
.width(80)
.height(80)
.backgroundColor(Color.Red)
}
build() {
Row({ space: 10 }) {
Button('Fancy')
.stateStyles({
normal: {
.width(100)
.height(100)
.backgroundColor(Color.Blue)
},
disabled: this.componentFancy,
pressed: globalFancy
})
}
}
}
```
![styles1](figures/styles1.gif)
## @Extend
The **@Extend** decorator adds new attribute methods to built-in components, such as **\<Text>**, **\<Column>**, and **\<Button>**. In this way, the built-in components are extended instantly.
```ts
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
.fontStyle(FontStyle.Italic)
.fontWeight(600)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text("Fancy")
.fancy(16)
Text("Fancy")
.fancy(24)
Text("Fancy")
.fancy(32)
}
}
}
```
> **NOTE**
>
> - The **@Extend** decorator cannot be defined inside the struct of a custom component.
> - The **@Extend** decorator supports only attribute methods.
![extend](figures/extend.PNG)
## @CustomDialog
The **@CustomDialog** decorator is used to decorate custom dialog boxes, enabling their content and styles to be dynamically set.
```ts
// xxx.ets
@CustomDialog
struct DialogExample {
controller: CustomDialogController
action: () => void
build() {
Row() {
Button('Close CustomDialog')
.onClick(() => {
this.controller.close()
this.action()
})
}.padding(20)
}
}
@Entry
@Component
struct CustomDialogUser {
dialogController: CustomDialogController = new CustomDialogController({
builder: DialogExample({ action: this.onAccept }),
cancel: this.existApp,
autoCancel: true
});
onAccept() {
console.info('onAccept');
}
existApp() {
console.info('Cancel dialog!');
}
build() {
Column() {
Button('Click to open Dialog')
.onClick(() => {
this.dialogController.open()
})
}
}
}
```
![customdialog](figures/customDialog.gif)
# Environment: Device Environment Query
You may want your application to behave differently based on the device environment where the application is running, for example, switching to dark mode or a specific language. In this case, you need Environment for device environment query.
Environment is a singleton object created by the ArkUI framework at application start. It provides a range of application state attributes to AppStorage that describe the device environment in which the application is running. Environment and its attributes are immutable. All property values are of simple type only.
## Application Scenarios
### Accessing Environment Parameters from UI
- Use **Environment.EnvProp** to save the environment variables of the device to AppStorage.
```ts
// Save the language code of the device to AppStorage. The default value is en.
// Whenever its value changes in the device environment, it will update its value in AppStorage.
Environment.EnvProp('languageCode', 'en');
```
- To keep a component variable updated with changes in the device environment, this variable should be decorated with \@StorageProp.
```ts
@StorageProp('languageCode') lang : string = 'en';
```
The chain of updates is as follows: Environment > AppStorage > Component.
> **NOTE**
> An \@StorageProp decorated variable can be locally modified, but the change will not be updated to AppStorage. This is because the environment variable parameters are read-only to the application.
```ts
// Save the device language code to AppStorage.
Environment.EnvProp('languageCode', 'en');
let enable = AppStorage.Get('languageCode');
@Entry
@Component
struct Index {
@StorageProp('languageCode') languageCode: string = 'en';
build() {
Row() {
Column() {
// Output the current device language code.
Text(this.languageCode)
}
}
}
}
```
### Using Environment in Application Logic
```ts
// Use Environment.EnvProp to save the device language code to AppStorage.
Environment.EnvProp('languageCode', 'en');
// Obtain the one-way bound languageCode variable from AppStorage.
const lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');
if (lang.get() === 'en') {
console.info('Hi');
} else {
console.info('Hello!');
}
```
# \@Extend: Extension of Built-in Components
Apart from\@Styles used to extend styles, AkrUI also provides \@Extend, which allows you to add a new attribute feature to a built-in component.
> **NOTE**
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Syntax
```ts
@Extend(UIComponentName) function functionName { ... }
```
### Rules of Use
- Unlike \@Styles, \@Extend can be defined only globally, that is, outside a component declaration.
- Unlike \@Styles, \@Extend can encapsulate private attributes and events of specified components and predefine \@Extend decorated methods of the same component.
```ts
// @Extend(Text) supports the private attribute fontColor of the <Text> component.
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText can call the predefined fancy method.
@Extend(Text) function superFancyText(size:number) {
.fontSize(size)
.fancy()
}
```
- Unlike \@Styles, \@Extend decorated methods support parameters. You can pass parameters when calling such methods. Regular TypeScript provisions for method parameters apply.
```ts
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(16)
Text('Fancy')
.fancy(24)
}
}
}
```
- A function can be passed as a parameter in an \@Extend decorated method to be used as the handler of the event.
```ts
@Extend(Text) function makeMeClick(onClick: () => void) {
.backgroundColor(Color.Blue)
.onClick(onClick)
}
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World';
onClickHandler() {
this.label = 'Hello ArkUI';
}
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.makeMeClick(this.onClickHandler.bind(this))
}
}
}
```
- A [state variable](arkts-state-management-overview.md) can be passed as a parameter in an \@Extend decorated method. When the state variable changes, the UI is updated and re-rendered.
```ts
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
@State fontSizeValue: number = 20
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(this.fontSizeValue)
.onClick(() => {
this.fontSizeValue = 30
})
}
}
}
```
## Application Scenarios
The following example declares three **\<Text>** components. The **fontStyle**, **fontWeight**, and **backgroundColor** styles are set for each **\<Text>** component.
```ts
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(100)
.backgroundColor(Color.Blue)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(200)
.backgroundColor(Color.Pink)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(300)
.backgroundColor(Color.Orange)
}.margin('20%')
}
}
```
\@Extend combines and reuses styles. The following is an example:
```ts
@Extend(Text) function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
```
With the use of \@Extend, the code readability is enhanced.
```ts
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(200, Color.Orange)
}.margin('20%')
}
}
```
# Getting Started with ArkTS
As its name implies, ArkTS is a superset of TypeScript. It is the preferred, primary programming language for application development in OpenHarmony.
- ArkTS offers all the features of TS.
ArkTS is the preferred main programming language for application development in OpenHarmony. ArkTS is a superset of [TypeScript](https://www.typescriptlang.org/) (TS for short). It contains all TS features and added features. Before getting started with ArkTS, it would be helpful if you have experience with TS development.
- ArkTS extends TS mainly by adding [declarative UI](arkts-basic-ui-description.md) capabilities, which allow you to develop high-performance applications in a more natural and intuitive manner.
The declarative UI capabilities offered by ArkTS include the following:
The added features offered by ArkTS include the following:
- [Basic UI description](arkts-basic-ui-description.md): A wide variety of decorators, custom components, and UI description mechanisms work with the built-in components, event methods, and attribute methods in ArkUI, jointly underpinning UI development.
- [State management](arkts-state-mgmt-page-level.md): In the multi-dimensional state management mechanism for ArkUI, UI-related data can be used not only within a component, but also be transferred between different component levels (for example, between parent and child components, between grandparent and grandchild components, or globally) in a device or even across devices. In addition, data transfer can be classified as one-way (read-only) or two-way (mutable). You can use these capabilities at your disposal to implement linkage between data and the UI.
- [Dynamic UI element building](arkts-dynamic-ui-elememt-building.md): In ArkTS, you can dynamically build UI elements, customizing the internal UI structure of components or extending the native components with custom component styles.
- [Rendering control](arkts-rendering-control.md): ArkTS provides conditional rendering and loop rendering. Conditional rendering can render state-specific content based on the application status. Loop rendering iteratively obtains data from the data source and creates the corresponding component during each iteration.
- [Restrictions and extensions](arkts-restrictions-and-extensions.md): ArkTS provides extensions, such as two-way binding. However, it is not without its restrictions.
- ArkTS will continue to evolve to accommodate changing application development and running requirements, and gradually adds more features, such as parallelism and concurrency enhancement, typed system enhancement, and distributed development paradigm.
- [Basic syntax](arkts-basic-syntax-overview.md): ArkTS defines declarative UI description, custom components, and dynamic extension of UI elements. All these, together with built-in components, event methods, and attribute methods in ArkUI, jointly underpin UI development.
Below is sample code to illustrate the building blocks of ArkTS. It implements a simple UI with two text segments, one divider, and one button. When the user clicks the button, the text content changes from "Hello World" to "Hello ArkUI".
- [State management](arkts-state-management-overview.md): In the multi-dimensional state management mechanism for ArkUI, UI-related data can be used not only within a component, but also be transferred between different component levels (for example, between parent and child components, between grandparent and grandchild components, or globally) in a device or even across devices. In addition, data transfer can be classified as one-way (read-only) or two-way (mutable). You can use these capabilities at your disposal to implement linkage between data and the UI.
![arkts-get-started](figures/arkts-get-started.png)
- [Rendering control](arkts-rendering-control-overview.md): ArkTS provides rendering control. Conditional rendering can render state-specific content based on the application status. **ForEach** iteratively obtains data from the data source and creates the corresponding component during each iteration. **LazyForEach** iterates over provided data sources and creates corresponding components during each iteration.
As shown above, ArkTS has the following building blocks:
- Decorators: used to decorate classes, structures, methods, and variables for custom definitions. In the preceding sample code, **@Entry**, **@Component**, and **@State** are decorators. Specifically, **@Component** indicates a custom component, **@Entry** indicates an entry component, and **@State** indicates a state variable in the component, whose change will trigger re-rendering of the UI.
- Custom components: reusable UI units that can be used in flexible combinations. In the preceding sample code, the structure **Hello** decorated by **@Component** is a custom component.
- UI description: declarative description of the UI structure, such as the code block of the **build()** method.
- Built-in components: basic, container, media, drawing, and canvas components preset in ArkTS. You can directly invoke such components as **\<Column>**, **\<Text>**, **\<Divider>**, and **\<Button>** components in the sample code.
- Attribute methods: methods used to configure component attributes, such as **fontSize()**, **width()**, **height()**, and **color()**. You can configure multiple attributes of a component in method chaining mode.
- Event methods: methods used to add the logic for a component to respond to an event. In the sample code, **onClick()** following **Button** is an event method. You can configure response logic for multiple events in method chaining mode.
ArkTS will continue to evolve to accommodate changing application development and running requirements, and gradually adds more features, such as parallelism and concurrency enhancement, typed system enhancement, and distributed development paradigm.
# \@Link: Two-Way Synchronization Between Parent and Child Components
An \@Link decorated variable can create two-way synchronization with a variable of its parent component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Overview
An \@Link decorated variable in a child component shares the same value with a variable in its parent component.
## Rules of Use
| \@Link Decorator| Description |
| ----------- | ---------------------------------------- |
| Decorator parameters | None. |
| Synchronization type | Two-way:<br>from an \@State, \@StorageLink, or \@Link decorated variable in the parent component to this variable; and the other way around.|
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).<br>The type must be specified and must be the same as that of the counterpart variable of the parent component.<br>**any** is not supported. A combination of simple and complex types is not supported. The **undefined** and **null** values are not allowed.<br>**NOTE**<br>The Length, ResourceStr, and ResourceColor types are a combination of simple and complex types and therefore not supported.|
| Initial value for the decorated variable | Forbidden. |
## Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Mandatory. A two-way synchronization relationship can be established with the @State, @StorageLink, or \@Link decorated variable in the parent component. An \@Link decorated variable can be initialized from an \@State, \@Link, \@Prop, \@Provide, \@Consume, \@ObjectLink, \@StorageLink, \@StorageProp, \@LocalStorageLink, or \@LocalStorageProp decorated variable in the parent component.<br>Since API version 9, the syntax is **Comp({&nbsp;aLink:&nbsp;this.aState&nbsp;})** for initializing an \@Link decorated variable in the child component from an @State decorated variable in its parent component. The **Comp({aLink:&nbsp;$aState})** syntax is also supported|
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | Private, accessible only within the component. |
**Figure 1** Initialization rule
![en-us_image_0000001502092556](figures/en-us_image_0000001502092556.png)
## Observed Changes and Behavior
### Observed Changes
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types).
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types).
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. For details, see [Array Type \@Link](#array-type-link).
### Framework Behavior
An \@Link decorated variable shares the lifecycle of its owning component.
To understand the value initialization and update mechanism of the \@Link decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the \@Link decorated variable (in this example, the \@State decorated variable in the parent component is used).
1. Initial render: The execution of the parent component's **build()** function creates a new instance of the child component. The initialization process is as follows:
1. An \@State decorated variable of the parent component must be specified to initialize the child component's \@Link decorated variable. The child component's \@Link decorated variable value and its source variable are kept in sync (two-way data synchronization).
2. The \@State state variable wrapper class of the parent component is passed to the child component through the build function. After obtaining the \@State state variable of the parent component, the \@Link wrapper class of the child component registers the **this** pointer to the current \@Link wrapper class with the \@State variable of the parent component.
2. Update of the \@Link source: When the state variable in the parent component is updated, the \@Link decorated variable in the related child component is updated. Processing steps:
1. As indicated in the initial rendering step, the child component's \@Link wrapper class registers the current **this** pointer with the parent component. When the \@State decorated variable in the parent component is changed, all system components (**elementid**) and state variables (such as the \@Link wrapper class) that depend on the parent component are traversed and updated.
2. After the \@Link wrapper class is updated, all system components (**elementId**) that depend on the \@Link decorated variable in the child component are notified of the update. In this way, the parent component has the state data of the child components synchronized.
3. Update of \@Link: After the \@Link decorated variable in the child component is updated, the following steps are performed (the \@State decorated variable in the parent component is used):
1. After the \@Link decorated variable is updated, the **set** method of the \@State wrapper class in the parent component is called to synchronize the updated value back to the parent component.
2. The \@Link in the child component and \@State in the parent component traverse the dependent system components and update the corresponding UI. In this way, the \@Link decorated variable in the child component is synchronized back to the \@State decorated variable in the parent component.
## Application Scenarios
### Example for @Link with Simple and Class Types
The following example is for \@Link of both the simple type and class type. After **Parent View: Set yellowButton** and **Parent View: Set GreenButton** in the parent component **ShufflingContainer** are clicked, the change in the parent component is synchronized to the child components. The change of the \@Link decorated variable in the child components **GreenButton** and **YellowButton** is also synchronized to the parent component.
```ts
class GreenButtonState {
width: number = 0;
constructor(width: number) {
this.width = width;
}
}
@Component
struct GreenButton {
@Link greenButtonState: GreenButtonState;
build() {
Button('Green Button')
.width(this.greenButtonState.width)
.height(150.0)
.backgroundColor('#00ff00')
.onClick(() => {
if (this.greenButtonState.width < 700) {
// Update the attribute of the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState.width += 125;
} else {
// Update the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState = new GreenButtonState(100);
}
})
}
}
@Component
struct YellowButton {
@Link yellowButtonState: number;
build() {
Button('Yellow Button')
.width(this.yellowButtonState)
.height(150.0)
.backgroundColor('#ffff00')
.onClick(() => {
// The change of the decorated variable of a simple type in the child component can be synchronized back to the parent component.
this.yellowButtonState += 50.0;
})
}
}
@Entry
@Component
struct ShufflingContainer {
@State greenButtonState: GreenButtonState = new GreenButtonState(300);
@State yellowButtonProp: number = 100;
build() {
Column() {
// Simple type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set yellowButton')
.onClick(() => {
this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 100 : 100;
})
// Class type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set GreenButton')
.onClick(() => {
this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
})
// Initialize the class type @Link.
GreenButton({ greenButtonState: $greenButtonState })
// Initialize the simple type @Link.
YellowButton({ yellowButtonState: $yellowButtonProp })
}
}
}
```
### Array Type \@Link
```ts
@Component
struct Child {
@Link items: number[];
build() {
Column() {
Button(`Button1: push`).onClick(() => {
this.items.push(this.items.length + 1);
})
Button(`Button2: replace whole item`).onClick(() => {
this.items = [100, 200, 300];
})
}
}
}
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3];
build() {
Column() {
Child({ items: $arr })
ForEach(this.arr,
item => {
Text(`${item}`)
},
item => item.toString()
)
}
}
}
```
As described above, the ArkUI framework can observe the addition, deletion, and replacement of array items. It should be noted that, in the preceding example, the type of the \@Link and \@State decorated variables is the same: number[]. It is not allowed to define the \@Link decorated variable in the child component as type number (**\@Link item: number**), and create child components for each array item in the \@State decorated array in the parent component. [\@Prop](arkts-prop.md) or \@Observed should be used depending on application semantics.
<!--no_check-->
# LocalStorage: UI State Storage
LocalStorage provides storage for the page-level UI state. The parameters of the LocalStorage type accepted through the \@Entry decorator share the same LocalStorage instance on the page. LocalStorage also allows for state sharing between pages within a UIAbility.
This topic describes only the LocalStorage application scenarios and related decorators: \@LocalStorageProp and \@LocalStorageLink.
> **NOTE**
>
> This module is supported since API version 9.
## Overview
LocalStorage is an in-memory "database" that ArkTS provides for storing state variables that are required to build pages of the application UI.
- An application can create multiple LocalStorage instances. These instances can be shared on a page or, by using the **GetShared** API from the UIAbility, across pages in a UIAbility.
- The root node of a component tree, that is, the \@Component decorated by \@Entry, can be assigned to a LocalStorage instance. All child instances of this custom component automatically gain access to the same LocalStorage instance.
- An \@Component decorated component has access to at most one LocalStorage instance and to [AppStorage](arkts-appstorage.md). A component not decorated with \@Entry cannot be assigned a LocalStorage instance. It can only accept a LocalStorage instance passed from its parent component through \@Entry. A LocalStorage instance can be assigned to multiple components in the component tree.
- All attributes in LocalStorage are mutable.
The application determines the lifecycle of a LocalStorage object. The JS Engine will garbage collect a LocalStorage object when the application releases the last reference to it, which includes deleting the last custom component.
LocalStorage provides two decorators based on the synchronization type of the component decorated with \@Component:
- [@LocalStorageProp](#localstorageprop): \@LocalStorageProp creates a one-way data synchronization from the named attribute in LocalStorage to the \@LocalStorageProp decorated variable.
- [@LocalStorageLink](#localstoragelink): \@LocalStorageLink creates a two-way data synchronization with the named attribute in the \@Component's LocalStorage.
## Restrictions
Once created, the type of a named attribute cannot be changed. Subsequent calls to **Set** must set a value of same type.
## \@LocalStorageProp
As mentioned above, if you want to establish a binding between LocalStorage and a custom component, you need to use the \@LocalStorageProp and \@LocalStorageLink decorators. Use \@LocalStorageProp(key) or \@LocalStorageLink(key) to decorate variables in the component. **key** identifies the attribute in LocalStorage.
When a custom component is initialized, the \@LocalStorageProp(key)/\@LocalStorageLink(key) decorated variable is initialized with the value of the attribute with the given key in LocalStorage. Local initialization is mandatory. If an attribute with the given key is missing from LocalStorage, it will be added with the stated initializing value. (Whether the attribute with the given key exists in LocalStorage depends on the application logic.)
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
By decorating a variable with \@LocalStorageProp(key), a one-way data synchronization is established with the attribute with the given key in LocalStorage. A local change can be made, but it will not besynchronized to LocalStorage. An update to the attribute with the given key in LocalStorage will overwrite local changes.
### Rules of Use
| \@LocalStorageProp Decorator| Description |
| ----------------------- | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | One-way: from the attribute in LocalStorage to the component variable. The component variable can be changed locally, but an update from LocalStorage will overwrite local changes.|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in LocalStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 1** \@LocalStorageProp initialization rule
![en-us_image_0000001501936014](figures/en-us_image_0000001501936014.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
- When the value change of the \@LocalStorageProp(key) decorated variable is observed, the change is not synchronized to the attribute with the give key value in LocalStorage.
- The value change of the \@LocalStorageProp(key) decorated variable only applies to the private member variables of the current component, but not other variables bound to the key.
- When the data decorated by \@LocalStorageProp(key) is a state variable, the change of the data is not synchronized to LocalStorage, but the owning custom component is re-rendered.
- When the attribute with the given key in LocalStorage is updated, the change is synchronized to all the \@LocalStorageProp(key) decorated data, and the local changes of the data are overwritten.
## \@LocalStorageLink
\@LocalStorageLink is required if you need to synchronize the changes of the state variables in a custom component back to LocalStorage.
\@LocalStorageLink(key) creates a two-way data synchronization with the attribute with the given key in LocalStorage.
1. If a local change occurs, it is synchronized to LocalStorage.
2. Changes in LocalStorage are synchronized to all attributes with the given key, including one-way bound variables (\@LocalStorageProp decorated variables and one-way bound variables created through \@Prop) and two-way bound variables (\@LocalStorageLink decorated variables and two-way bound variables created through \@Link).
### Rules of Use
| \@LocalStorageLink Decorator| Description |
| ----------------------- | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | Two-way: from the attribute in LocalStorage to the custom component variable and back|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in LocalStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize a n \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 2** \@LocalStorageLink initialization rule
![en-us_image_0000001552855957](figures/en-us_image_0000001552855957.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
1. When the value change of the \@LocalStorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the give key value in LocalStorage.
2. Once the attribute with the given key in LocalStorage is updated, all the data (including \@LocalStorageLink and \@LocalStorageProp decorated variables) bound to the attribute key is changed synchronously.
3. When the data decorated by \@LocalStorageProp(key) is a state variable, the change of the data is synchronized to LocalStorage, and the owning custom component is re-rendered.
## Application Scenarios
### Example of Using LocalStorage in Application Logic
```ts
let storage = new LocalStorage({ 'PropA': 47 }); // Create a new instance and initialize it with the given object.
let propA = storage.get('PropA') // propA == 47
let link1 = storage.link('PropA'); // link1.get() == 47
let link2 = storage.link('PropA'); // link2.get() == 47
let prop = storage.prop('PropA'); // prop.get() = 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get()=1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
```
### Example for Using LocalStorage from Inside the UI
The two decorators \@LocalStorageProp and \@LocalStorageLink can work together to obtain the state variable stored in a LocalStorage instance in the UI component.
This example uses \@LocalStorage as an example to show how to:
- Use the **build** function to create a LocalStorage instance named **storage**.
- Use the \@Entry decorator to add **storage** to the top-level component **CompA**.
- Use \@LocalStorageLink to create a two-way data synchronization with the given attribute in LocalStorage.
```ts
// Create a new instance and initialize it with the given object.
let storage = new LocalStorage({ 'PropA': 47 });
@Component
struct Child {
// @LocalStorageLink creates a two-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageLink('PropA') storLink2: number = 1;
build() {
Button(`Child from LocalStorage ${this.storLink2}`)
// The changes will be synchronized to ProA in LocalStorage and with Parent.storLink1.
.onClick(() => this.storLink2 += 1)
}
}
// Make LocalStorage accessible from the @Component decorated component.
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink creates a two-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageLink('PropA') storLink1: number = 1;
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStorage ${this.storLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already
.onClick(() => this.storLink1 += 1)
// The @Component decorated child component automatically obtains access to the CompA LocalStorage instance.
Child()
}
}
}
```
### Simple Example of Using \@LocalStorageProp with LocalStorage
In this example, the **CompA** and **Child** components create local data that is one-way synced with the PropA attribute in the LocalStorage instance **storage**.
- The change of **this.storProp1** in **CompA** takes effect only in **CompA** and is not synchronized to **storage**.
- In the **Child** component, the value of **storProp2** bound to **Text** is still 47.
```ts
// Create a new instance and initialize it with the given object.
let storage = new LocalStorage({ 'PropA': 47 });
// Make LocalStorage accessible from the @Component decorated component.
@Entry(storage)
@Component
struct CompA {
// @LocalStorageProp creates a one-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageProp('PropA') storProp1: number = 1;
build() {
Column({ space: 15 }) {
// The initial value is 47. After the button is clicked, the value is incremented by 1. The change takes effect only in storProp1 in the current component and is not synchronized to LocalStorage.
Button(`Parent from LocalStorage ${this.storProp1}`)
.onClick(() => this.storProp1 += 1)
Child()
}
}
}
@Component
struct Child {
// @LocalStorageProp creates a one-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageProp('PropA') storProp2: number = 2;
build() {
Column({ space: 15 }) {
// When CompA changes, the current storProp2 does not change, and 47 is displayed.
Text(`Parent from LocalStorage ${this.storProp2}`)
}
}
}
```
### Simple Example of Using \@LocalStorageLink and LocalStorage
This example shows how to create a two-way data synchronization between an \@LocalStorageLink decorated variable and LocalStorage.
```ts
// Create a LocalStorage instance.
let storage = new LocalStorage({ 'PropA': 47 });
// Invoke the link9+ API to create a two-way data synchronization with PropA. linkToPropA is a global variable.
let linkToPropA = storage.link('PropA');
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink('PropA') creates a two-way synchronization with PropA in the CompA custom component. The initial value is 47, because PropA has been set to 47 during LocalStorage construction.
@LocalStorageLink('PropA') storLink: number = 1;
build() {
Column() {
Text(`incr @LocalStorageLink variable`)
// Clicking incr @LocalStorageLink variable increases the value of this.storLink by 1. The change is synchronized back to the storage. The global variable linkToPropA also changes.
.onClick(() => this.storLink += 1)
// You are not advised to use the global variable linkToPropA.get() in the component because errors may occur due to different life cycles.
Text(`@LocalStorageLink: ${this.storLink} - linkToPropA: ${linkToPropA.get()}`)
}
}
}
```
### State Variable Synchronization Between Sibling Nodes
This example shows how to use \@LocalStorageLink to create a two-way synchronization for the state between sibling nodes.
Check the changes in the **Parent** custom component.
1. Clicking **countStorage ${this.playCount} incr by 1** decreases the value of **this.playCount** by 1. This change is synchronized to LocalStorage and to the components bound to **playCountLink** in the **Child** component.
2. Click **countStorage ${this.playCount} incr by 1** to call the **set** API in LocalStorage to update the attributes corresponding to **countStorage** in LocalStorage. The components bound to** playCountLink** in the **Child** component are updated synchronously.
3. The **playCount in LocalStorage for debug ${storage.get&lt;number&gt;('countStorage')}** **\<Text>** component is not updated synchronously, because **storage.get<number>('countStorage')** returns a regular variable. The update of a regular variable does not cause the **\<Text>** component to be re-rendered.
Changes in the **Child** custom component:
1. The update of **playCountLink** is synchronized to LocalStorage, and the parent and sibling child custom components are re-rendered accordingly.
```ts
let storage = new LocalStorage({ countStorage: 1 });
@Component
struct Child {
// Name the child component instance.
label: string = 'no name';
// Two-way synchronization with countStorage in LocalStorage.
@LocalStorageLink('countStorage') playCountLink: number = 0;
build() {
Row() {
Text(this.label)
.width(50).height(60).fontSize(12)
Text(`playCountLink ${this.playCountLink}: inc by 1`)
.onClick(() => {
this.playCountLink += 1;
})
.width(200).height(60).fontSize(12)
}.width(300).height(60)
}
}
@Entry(storage)
@Component
struct Parent {
@LocalStorageLink('countStorage') playCount: number = 0;
build() {
Column() {
Row() {
Text('Parent')
.width(50).height(60).fontSize(12)
Text(`playCount ${this.playCount} dec by 1`)
.onClick(() => {
this.playCount -= 1;
})
.width(250).height(60).fontSize(12)
}.width(300).height(60)
Row() {
Text('LocalStorage')
.width(50).height(60).fontSize(12)
Text(`countStorage ${this.playCount} incr by 1`)
.onClick(() => {
storage.set<number>('countStorage', 1 + storage.get<number>('countStorage'));
})
.width(250).height(60).fontSize(12)
}.width(300).height(60)
Child({ label: 'ChildA' })
Child({ label: 'ChildB' })
Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
.width(300).height(60).fontSize(12)
}
}
}
```
### Sharing a LocalStorage Instance from UIAbility to One or More Pages
In the preceding examples, the LocalStorage instance is shared only in an \@Entry decorated component and its owning child component (a page). To enable a LocalStorage instance to be shared across pages, you can create a LocalStorage instance in the owning UIAbility and call windowStage.[loadContent](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis/js-apis-window.md#loadcontent9).
```ts
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
storage: LocalStorage = new LocalStorage({
'PropA': 47
});
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}
```
On the page, call the **GetShared** API to obtain the LocalStorage instance shared through **loadContent**.
```ts
// Use the GetShared API to obtain the Storage instance shared by stage.
let storage = LocalStorage.GetShared()
@Entry(storage)
@Component
struct CompA {
// can access LocalStorage instance using
// @LocalStorageLink/Prop decorated variables
@LocalStorageLink('PropA') varA: number = 1;
build() {
Column() {
Text(`${this.varA}`).fontSize(50)
}
}
}
```
> **NOTE**
>
> It is good practice to always create a LocalStorage instance with meaningful default values, which serve as a backup when execution exceptions occur and are also useful for unit testing of pages.
<!--no_check-->
# \@Observed and \@ObjectLink: Observing Attribute Changes in Nested Class Objects
The decorators described above can observe only the changes of the first layer. However, in real-world application development, the application may encapsulate its own data model based on development requirements. In the case of multi-layer nesting, for example, a two-dimensional array, an array item class, or a class insider another class as an attribute, the attribute changes at the second layer cannot be observed. This is where the \@Observed and \@ObjectLink decorators come in handy.
> **NOTE**
>
> Since API version 9, these two decorators are supported in ArkTS widgets.
## Overview
\@ObjectLink and \@Observed class decorators are used for two-way data synchronization in scenarios involving nested objects or arrays:
- Regarding classes decorated by \@Observed, the attribute changes can be observed.
- The \@ObjectLink decorated state variable in the child component is used to accept the instance of the \@Observed decorated class and establish two-way data binding with the corresponding state variable in the parent component. The instance can be an \@Observed decorated item in the array or an \@Observeddecorated attribute in the class object.
- Using \@Observed alone has no effect. Combined use with \@ObjectLink for two-way synchronization or with [\@Prop](arkts-prop.md) for one-way synchronization is required.
## Restrictions
Using \@Observed to decorate a class changes the original prototype chain of the class. Using \@Observed and other class decorators to decorate the same class may cause problems.
## Decorator Description
| \@Observed Decorator| Description |
| -------------- | --------------------------------- |
| Decorator parameters | None. |
| Class decorator | Decorates a class. You must use **new** to create a class object before defining the class.|
| \@ObjectLink Decorator| Description |
| ----------------- | ---------------------------------------- |
| Decorator parameters | None. |
| Synchronization type | No synchronization with the parent component. |
| Allowed variable types | Objects of \@Observed decorated classes. The type must be specified.<br>Simple type variables are not supported. Use [\@Prop](arkts-prop.md) instead.<br>An \@ObjectLink decorated variable accepts changes to its attributes, but assignment is not allowed. In other words, an \@ObjectLink decorated variable is read-only and cannot be changed.|
| Initial value for the decorated variable | Not allowed. |
Example of a read-only \@ObjectLink decorated variable:
```ts
// The \@ObjectLink decorated variable accepts changes to its attribute.
this.objLink.a= ...
// Value assignment is not allowed for the \@ObjectLink decorated variable.
this.objLink= ...
```
> **NOTE**
>
> Value assignment is not allowed for the \@ObjectLink decorated variable. To assign a value, use [@Prop](arkts-prop.md) instead.
>
> - \@Prop creates a one-way synchronization from the data source to the decorated variable. It takes a copy of its source tp enable changes to remain local. When \@Prop observes a change to its source, the local value of the \@Prop decorated variable is overwritten.
>
> - \@ObjectLink creates a two-way synchronization between the data source and the decorated variable. An \@ObjectLink decorated variable can be considered as a pointer to the source object inside the parent component. If value assignment of an \@ObjectLink decorated variable occurs, the synchronization chain is interrupted.
## Variable Transfer/Access Rules
| \@ObjectLink Transfer/Access| Description |
| ----------------- | ---------------------------------------- |
| Initialization from the parent component | Mandatory.<br>To initialize an \@ObjectLink decorated variable, a variable in the parent component must meet all the following conditions:<br>- The variable type is an \@Observed decorated class.<br>- The initialized value must be an array item or a class attribute.<br>- The class or array of the synchronization source must be decorated by \@State, \@Link, \@Provide, \@Consume, or \@ObjectLink.<br>For an example where the synchronization source is an array item, see [Object Array](#object-array). For an example of the initialized class, see [Nested Object](#nested-object).|
| Synchronize with the source | Two-way. |
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
**Figure 1** Initialization rule
![en-us_image_0000001502255262](figures/en-us_image_0000001502255262.png)
## Observed Changes and Behavior
### Observed Changes
If the attribute of an \@Observed decorated class is not of the simple type, such as class, object, or array, it must be decorated by \@Observed. Otherwise, the attribute changes cannot be observed.
```ts
class ClassA {
public c: number;
constructor(c: number) {
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
public b: number;
constructor(a: ClassA, b: number) {
this.a = a;
this.b = b;
}
}
```
In the preceding example, **ClassB** is decorated by \@Observed, and the value changes of its member variables can be observed. In contrast, **ClassA** is not decorated by \@Observed, and therefore its attribute changes cannot be observed.
```ts
@ObjectLink b: ClassB
// The value assignment can be observed.
this.b.a = new ClassA(5)
this.b.b = 5
// ClassA is not decorated by @Observed, and its attribute changes cannot be observed.
this.b.a.c = 5
```
\@ObjectLink: \@ObjectLink can only accept instances of classes decorated by \@Observed. The following can be observed:
- Value changes of the attributes that **Object.keys(observedObject)** returns. For details, see [Nested Object](#nested-object).
- Replacement of array items for the data source of an array and changes of class attributes for the data source of a class. For details, see [Object Array](#object-array).
### Framework Behavior
1. Initial render:
1. \@Observed causes all instances of the decorated class to be wrapped with an opaque proxy object, which takes over the setter and getter methods of the attributes on the class.
2. The \@ObjectLink decorated variable in the child component is initialized from the parent component and accepts the instance of the \@Observed decorated class. The \@ObjectLink decorated wrapped object registers itself with the \@Observed decorated class.
2. Attribute update: When the attribute of the \@Observed decorated class is updated, the system uses the setter and getter of the proxy, traverses the \@ObjectLink decorated wrapped objects that depend on it, and notifies the data update.
## Application Scenarios
### Nested Object
The following is the data structure of a nested class object.
```ts
// objectLinkNestedObjects.ets
let NextID: number = 1;
@Observed
class ClassA {
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
constructor(a: ClassA) {
this.a = a;
}
}
```
The following component hierarchy presents this data structure.
```ts
@Component
struct ViewA {
label: string = 'ViewA1';
@ObjectLink a: ClassA;
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c=${this.a.c} +1`)
.onClick(() => {
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
@State b: ClassB = new ClassB(new ClassA(0));
build() {
Column() {
ViewA({ label: 'ViewA #1', a: this.b.a })
ViewA({ label: 'ViewA #2', a: this.b.a })
Button(`ViewB: this.b.a.c+= 1`)
.onClick(() => {
this.b.a.c += 1;
})
Button(`ViewB: this.b.a = new ClassA(0)`)
.onClick(() => {
this.b.a = new ClassA(0);
})
Button(`ViewB: this.b = new ClassB(ClassA(0))`)
.onClick(() => {
this.b = new ClassB(new ClassA(0));
})
}
}
}
```
Event handlers in **ViewB**:
- this.b.a = new ClassA(0) and this.b = new ClassB(new ClassA(0)): Change to the \@State decorated variable **b** and its attributes.
- this.b.a.c = ... : Second change. [@State](arkts-state.md#observed-changes) cannot observe the change of the second layer, but ClassA is decorated by \@Observed, and therefore the change of its attribute c can be observed by \@ObjectLink.
Event handlers in **ViewA**:
- this.a.c += 1: Changes to the \@ObjectLink decorated variable which cause the button label to be updated. Unlike \@Prop, \@ObjectLink does not have a copy of its source. Instead, \@ObjectLink creates a reference to its source.
- The \@ObjectLink decorated variable is read-only. Assigning **this.a = new ClassA(...)** is not allowed. Once value assignment occurs, the reference to the data source is reset and the synchronization is interrupted.
### Object Array
An object array is a frequently used data structure. The following example shows the usage of array objects.
```ts
@Component
struct ViewA {
// The type of @ObjectLink of the child component ViewA is ClassA.
@ObjectLink a: ClassA;
label: string = 'ViewA1';
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c = ${this.a.c} +1`)
.onClick(() => {
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
// ViewB has the @State decorated ClassA[].
@State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
build() {
Column() {
ForEach(this.arrA,
(item) => {
ViewA({ label: `#${item.id}`, a: item })
},
(item) => item.id.toString()
)
// Initialize the @ObjectLink decorated variable using the array item in the @State decorated array, which is an instance of ClassA decorated by @Observed.
ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
Button(`ViewB: reset array`)
.onClick(() => {
this.arrA = [new ClassA(0), new ClassA(0)];
})
Button(`ViewB: push`)
.onClick(() => {
this.arrA.push(new ClassA(0))
})
Button(`ViewB: shift`)
.onClick(() => {
this.arrA.shift()
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
})
}
}
}
```
- this.arrA[Math.floor(this.arrA.length/2)] = new ClassA(..): The change of this state variable triggers two updates.
1. ForEach: The value assignment of the array item causes the change of [itemGenerator](arkts-rendering-control-foreach.md#api-description) of **ForEach**. Therefore, the array item is identified as changed, and the item builder of ForEach is executed to create a **ViewA** component instance.
2. ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] }): The preceding update changes the first element in the array. Therefore, the **ViewA** component instance bound to **this.arrA[0]** is updated.
- this.arrA.push(new ClassA(0)): The change of this state variable triggers two updates with different effects.
1. ForEach: The newly added Class A object is unknown to the **ForEach** [itemGenerator](arkts-rendering-control-foreach.md#api-description). The item builder of **ForEach** will be executed to create a **View A** component instance.
2. ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] }): The last item of the array is changed. As a result, the second **View A** component instance is changed. For **ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] })**, a change to the array does not trigger a change to the array item, so the first **View A** component instance is not refreshed.
- this.arrA[Math.floor (this.arrA.length/2)].c: [@State](arkts-state.md#observed-changes) cannot observe changes in the second layer. However, as **ClassA** is decorated by \@Observed, the change of its attributes will be observed by \@ObjectLink.
### Two-Dimensional Array
@Observed class decoration is required for a two-dimensional array. You can declare an \@Observed decorated class that extends from **Array**.
```ts
@Observed
class StringArray extends Array<String> {
}
```
Declare a class that extends from** Array**: **class StringArray extends Array\<String> {}** and create an instance of **StringArray**. The use of the **new** operator is required for the \@Observed class decorator to work properly.
```ts
@Observed
class StringArray extends Array<String> {
}
@Component
struct ItemPage {
@ObjectLink itemArr: StringArray;
build() {
Row() {
Text('ItemPage')
.width(100).height(100)
ForEach(this.itemArr,
item => {
Text(item)
.width(100).height(100)
},
item => item
)
}
}
}
@Entry
@Component
struct IndexPage {
@State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()];
build() {
Column() {
ItemPage({ itemArr: this.arr[0] })
ItemPage({ itemArr: this.arr[1] })
ItemPage({ itemArr: this.arr[2] })
Divider()
ForEach(this.arr,
itemArr => {
ItemPage({ itemArr: itemArr })
},
itemArr => itemArr[0]
)
Divider()
Button('update')
.onClick(() => {
console.error('Update all items in arr');
if (this.arr[0][0] !== undefined) {
// We should have a real ID to use with ForEach, but we do no.
// Therefore, we need to make sure the pushed strings are unique.
this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`);
this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`);
this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`);
} else {
this.arr[0].push('Hello');
this.arr[1].push('World');
this.arr[2].push('!');
}
})
}
}
}
```
# Overview of Other State Management Features
In addition to the component state and application state management mentioned in previous topics, ArkTS also provides \@Watch and $$ for state management.
- [\@Watch](arkts-watch.md) is used to listen for the changes of state variables.
- [$$operator](arkts-two-way-sync.md) provides a TS variable by-reference to a built-in component so that the variable value and the internal state of that component are kept in sync.
此差异已折叠。
此差异已折叠。
# Overview of Rendering Control
ArkUI uses the **build()** function of [custom components](arkts-create-custom-components.md) and declarative UI description statements in the [@builder decorator](arkts-builder.md) to build the corresponding UI. In declarative description statements, you can use rendering control statements in addition to system components to accelerate UI construction. These rendering control statements include conditional statements that control whether components are displayed, rendering statements for repeated content that quickly generate components based on array data, and lazy loading statements for scenarios involving a large amount of data.
# Basic Concepts
In the multi-dimensional state management mechanism for ArkUI, UI-related data can be used not only within a component, but also be transferred between different component levels (for example, between parent and child components, between grandparent and grandchild components, or globally). In addition, data transfer can be classified as one-way (read-only) or two-way (mutable). You can use these capabilities at your disposal to implement linkage between data and the UI.
![](figures/CoreSpec_figures_state-mgmt-overview.png)
## State Management with Page-level Variables
| Decorator | Decorates... | Description |
| ----------- | ------------------------- | ------------------------------------------------------------ |
| @State | Primitive data types, classes, and arrays | If the decorated state data is modified, the **build** method of the component will be called to update the UI. |
| @Prop | Primitive data types | This decorator is used to establish one-way data binding between the parent and child components. When the data associated with the parent component is modified, the UI of the current component is re-rendered.|
| @Link | Primitive data types, classes, and arrays | This decorator is used to establish two-way data binding between the parent and child components. The internal state data of the parent component is used as the data source. Any changes made to one component will be reflected to the other.|
| @Observed | Class | This decorator is used to indicate that the data changes in the class will be managed by the UI page. |
| @ObjectLink | Objects of **@Observed** decorated classes| When the decorated state variable is modified, the parent and sibling components that have the state variable will be notified for UI re-rendering.|
| @Provide | Primitive data types, classes, and arrays | As the data provider, **@Provide** can update the data of child nodes and trigger page re-rendering.|
| @Consume | Primitive data types, classes, and arrays | When the **@Consume** decorated variable detects the update of the **@Provide** decorated variable, the re-rendering of the current custom component is triggered.|
## State Management with Application-level Variables
**AppStorage** is the central store of the application states in the entire UI. ArkUI creates a singleton **AppStorage** object for the application and provides the corresponding decorators and APIs for the application.
- **@StorageLink**: works in a way similar to that of **@Consume**. The difference is that the target object is obtained from the **AppStorage** based on the given name. **@StorageLink** establishes two-way binding between the decorated UI component and **AppStorage** to synchronize data.
- **@StorageProp**: synchronizes UI component attributes with the **AppStorage** unidirectionally. That is, the value change in the **AppStorage** will trigger an update of the corresponding UI component, but the change of the UI component will not cause an update of the attribute value in the **AppStorage**.
- Service logic implementation API: adds, reads, modifies, or deletes the state data of applications. The changes made by this API will be synchronized to the UI component for UI update.
- **LocalStorage**: provides ability-specific storage.
- **@LocalStorageLink**: establishes two-way data binding between a component and the **LocalStorage**. Specifically, this is achieved by decorating the component's state variable with **@LocalStorageLink(*key*)**. Wherein, **key** is the attribute key value in the **LocalStorage**.
- **@LocalStorageProp**: establishes one-way data binding between a component and the **LocalStorage**. Specifically, this is achieved by decorating the component's state variable with **@LocalStorageProp(*key*)**. Wherein, **key** is the attribute key value in the **LocalStorage**.
- **PersistentStorage**: provides a set of static methods for managing persistent data of applications. Persistent data with specific tags can be linked to the **AppStorage**, and then the persistent data can be accessed through the **AppStorage** APIs. Alternatively, the **@StorageLink** decorator can be used to access the variable that matches the specific key.
- **Environment**: provides the **AppStorage** with an array of environment state attributes that are required by the application and describe the device environment where the application runs. It is a singleton object created by the framework when the application is started.
For details about how to use state variables, see [Restrictions on Data Type Declarations of State Variables](arkts-restrictions-and-extensions.md).
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# Atomic Service
# Atomic Service Development
## Pre-loading by HAP Type
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册