1# AppStorage: Storing Application-wide UI State 2 3 4AppStorage provides central storage for application UI state attributes. It is bound to the application process and is created by the UI framework at application startup. 5 6 7Unlike AppStorage, LocalStorage is usually used for page-level state sharing, While 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. 8 9 10This topic describes the AppStorage use scenarios and related decorators: \@StorageProp and \@StorageLink. 11 12 13Different from decorators such as \@State that can be passed only in the component tree, AppStorage aims to provide basic data sharing across abilities in a larger scope. Before reading this topic, you are advised to read [State Management Overview](./arkts-state-management-overview.md) to have a macro understanding of the positioning of AppStorage in the state management framework. 14 15AppStorage also provides APIs for you to manually add, delete, change, and query keys of AppStorage outside the custom component. You are advised to read this topic together with [AppStorage API reference](../reference/apis-arkui/arkui-ts/ts-state-management.md#appstorage). 16 17 18## Overview 19 20AppStorage is a singleton object that is created at application startup. Its purpose is to provide central storage for application UI state attributes. AppStorage retains all those attributes and their values as long as the application remains running. Each attribute is accessed using a unique key, which is a string. 21 22UI components synchronize application state attributes with AppStorage. AppStorage can be accessed during implementation of application service logic as well. 23 24AppStorage supports state sharing among multiple UIAbility instances in the [main thread](../application-models/thread-model-stage.md) of an application. 25 26Selected state attributes of AppStorage can be synchronized 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 service logic, and separated from the UI. Link to [@StorageProp](#storageprop) and [@StorageLink](#storagelink) those AppStorage attributes whose values should be kept until application re-start. 27 28 29## \@StorageProp 30 31As mentioned above, if you want to establish a binding between AppStorage and a custom component, you'll need the \@StorageProp or \@StorageLink decorator. Use \@StorageProp(key) or \@StorageLink(key) to decorate variables in the component, where **key** identifies an attribute in AppStorage. 32 33When a custom component is initialized, the attribute value corresponding to the key in AppStorage is used to initialize the \@StorageProp(key) or \@StorageLink(key) decorated variable. Whether the attribute with the given key exists in AppStorage depends on the application logic. This means that it may be missing from AppStorage. In light of this, local initialization is mandatory for the \@StorageProp(key) or \@StorageLink(key) decorated variable. 34 35By decorating a variable with \@StorageProp(key), a one-way data synchronization is established from the attribute with the given key in AppStorage to the variable. 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. 36> **NOTE** 37> 38> This decorator can be used in atomic services since API version 11. 39 40### Rules of Use 41 42| \@StorageProp Decorator| Description | 43| ----------------------- | ------------------------------------------------------------ | 44| Decorator parameters | **key**: constant string, mandatory (the string must be quoted) | 45| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types.<br>(Applicable to API version 12 or later) Map, Set, and Date types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified. Whenever possible, use the same type as that of the corresponding attribute in AppStorage. Otherwise, implicit type conversion occurs, which may cause application behavior exceptions.<br>**any** is not supported. **undefined** and **null** are supported since API version 12.<br>(Applicable to API version 12 or later) Union type of the preceding types, for example, **string \| number**, **string \| undefined**, or **ClassA \| null**. For details, see [Union Type](#union-type).<br>**NOTE**<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@StorageProp("AA") a: number \| null = null** is recommended; **@StorageProp("AA") a: number = null** is not recommended.| 46| 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.| 47| Initial value for the decorated variable | Mandatory. If the attribute does not exist in AppStorage, it will be created and initialized with this value.| 48 49 50### Variable Transfer/Access Rules 51 52| Transfer/Access | Description | 53| ---------- | ---------------------------------------- | 54| Initialization and update from the parent component| Forbidden. Only the attribute corresponding to the key in AppStorage can be initialized. If there is no corresponding key, the local default value is used for initialization.| 55| Child component initialization | Supported. The \@StorageProp decorated variable can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.| 56| Access from outside the component | Not supported. | 57 58 59 **Figure 1** \@StorageProp initialization rule 60 61 62 63 64 65### Observed Changes and Behavior 66 67**Observed Changes** 68 69 70- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. 71 72- When the decorated variable is of the class or object type, its value change as well as value changes of all its attributes can be observed. For details, see [Example of Using AppStorage and LocalStorage Inside the UI](#example-of-using-appstorage-and-localstorage-inside-the-ui). 73 74- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 75 76- When the decorated variable is of the Date type, the value change of the **Date** object can be observed, and the following APIs can be called to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type). 77 78- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type). 79 80- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type). 81 82 83**Framework Behavior** 84 85 86- When the value change of the \@StorageProp(key) decorated variable is observed, the change is not synchronized to the attribute with the given key in AppStorage. 87 88- 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. 89 90- 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. 91 92- 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. 93 94 95## \@StorageLink 96 97> **NOTE** 98> 99> This decorator can be used in atomic services since API version 11. 100 101\@StorageLink(key) creates a two-way data synchronization between the variable it decorates and the attribute with the given key in AppStorage. 102 1031. Local changes are synchronized to AppStorage. 104 1052. Any change in AppStorage is synchronized to the attribute with the given key in all scenarios, 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). 106 107### Rules of Use 108 109| \@StorageLink Decorator| Description | 110| ------------------ | ---------------------------------------- | 111| Decorator parameters | **key**: constant string, mandatory (the string must be quoted) | 112| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types.<br>(Applicable to API version 12 or later) Map, Set, and Date types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior-1).<br>The type must be specified. Whenever possible, use the same type as that of the corresponding attribute in AppStorage. Otherwise, implicit type conversion occurs, which may cause application behavior exceptions.<br>**any** is not supported. **undefined** and **null** are supported since API version 12.<br>(Applicable to API version 12 or later) Union type of the preceding types, for example, **string \| number**, **string \| undefined**, or **ClassA \| null**. For details, see [Union Type](#union-type).<br>**NOTE**<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@StorageLink("AA") a: number \| null = null** is supported, but **@StorageLink("AA") a: number = null** is not.| 113| Synchronization type | Two-way: from the attribute in AppStorage to the custom component variable and vice versa| 114| Initial value for the decorated variable | Mandatory. If the attribute does not exist in AppStorage, it will be created and initialized with this value.| 115 116 117### Variable Transfer/Access Rules 118 119| Transfer/Access | Description | 120| ---------- | ---------------------------------------- | 121| Initialization and update from the parent component| Forbidden. | 122| Child component initialization | Supported. The \@StorageLink decorated variable can be used to initialize a regular variable or an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.| 123| Access from outside the component | Not supported. | 124 125 126 **Figure 2** \@StorageLink initialization rule 127 128 129 130 131 132### Observed Changes and Behavior 133 134**Observed Changes** 135 136 137- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. 138 139- When the decorated variable is of the class or object type, its value change as well as value changes of all its attributes can be observed. For details, see [Example of Using AppStorage and LocalStorage Inside the UI](#example-of-using-appstorage-and-localstorage-inside-the-ui). 140 141- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 142 143- When the decorated variable is of the Date type, the value change of the **Date** object can be observed, and the following APIs can be called to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type). 144 145- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type). 146 147- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type). 148 149 150**Framework Behavior** 151 152 1531. When the value change of the \@StorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the given key in AppStorage. 154 1552. Once the attribute with the given key in AppStorage is updated, all the data (including \@StorageLink and \@StorageProp decorated variables) bound to the key is changed synchronously. 156 1573. When the data decorated by \@StorageLink(key) is a state variable, its change is synchronized to AppStorage, and the owning custom component is re-rendered. 158 159 160## Restrictions 161 1621. The parameter of \@StorageProp and \@StorageLink must be of the string type. Otherwise, an error is reported during compilation. 163 164 ```ts 165 AppStorage.setOrCreate('PropA', 47); 166 167 // Incorrect format. An error is reported during compilation. 168 @StorageProp() storageProp: number = 1; 169 @StorageLink() storageLink: number = 2; 170 171 // Correct format. 172 @StorageProp('PropA') storageProp: number = 1; 173 @StorageLink('PropA') storageLink: number = 2; 174 ``` 175 1762. \@StorageProp and \@StorageLink cannot decorate variables of the function type. Otherwise, the framework throws a runtime error. 177 1783. When using AppStorage together with [PersistentStorage](arkts-persiststorage.md) and [Environment](arkts-environment.md), pay attention to the following: 179 180 (1) After an attribute is created in AppStorage, a call to **PersistentStorage.persistProp()** uses the attribute 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 an Attribute in AppStorage Before PersistentStorage](arkts-persiststorage.md#accessing-an-attribute-in-appstorage-after-persistentstorage). 181 182 (2) After an attribute is created in AppStorage, a call to **Environment.envProp()** with the same attribute name will fail. This is because environment variables will not be written into AppStorage. Therefore, you are advised not to use the preset environment variable names in AppStorage. 183 184 (3) Changes to the variables decorated by state decorators will cause UI re-rendering. If the changes are for message communication, rather than for UI re-rendering, the emitter mode is recommended. For the example, see <!--Del-->[<!--DelEnd-->**Unrecommended: Using @StorageLink to Implement Event Notification**<!--Del-->](#unrecommended-using-storagelink-to-implement-event-notification)<!--DelEnd-->. 185 186 187## Use Scenarios 188 189 190### Example of Using AppStorage and LocalStorage in Application Logic 191 192Since AppStorage is a singleton, its APIs are all static. How these APIs work resembles the non-static APIs of LocalStorage. 193 194 195```ts 196AppStorage.setOrCreate('PropA', 47); 197 198let storage: LocalStorage = new LocalStorage(); 199storage.setOrCreate('PropA',17); 200let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17 201let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47 202let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47 203let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47 204 205link1.set(48); // Two-way synchronization: link1.get() == link2.get() == prop.get() == 48 206prop.set(1); // One-way synchronization: prop.get() == 1; but link1.get() == link2.get() == 48 207link1.set(49); // Two-way synchronization: link1.get() == link2.get() == prop.get() == 49 208 209storage.get<number>('PropA') // == 17 210storage.set('PropA', 101); 211storage.get<number>('PropA') // == 101 212 213AppStorage.get<number>('PropA') // == 49 214link1.get() // == 49 215link2.get() // == 49 216prop.get() // == 49 217``` 218 219 220### Example of Using AppStorage and LocalStorage Inside the UI 221 222\@StorageLink works together with AppStorage in the same way as \@LocalStorageLink works together with LocalStorage. It creates two-way data synchronization with an attribute in AppStorage. 223 224 225```ts 226class Data { 227 code: number; 228 229 constructor(code: number) { 230 this.code = code; 231 } 232} 233 234AppStorage.setOrCreate('PropA', 47); 235AppStorage.setOrCreate('PropB', new Data(50)); 236let storage = new LocalStorage(); 237storage.setOrCreate('LinkA', 48); 238storage.setOrCreate('LinkB', new Data(100)); 239 240@Entry(storage) 241@Component 242struct Index { 243 @StorageLink('PropA') storageLink: number = 1; 244 @LocalStorageLink('LinkA') localStorageLink: number = 1; 245 @StorageLink('PropB') storageLinkObject: Data = new Data(1); 246 @LocalStorageLink('LinkB') localStorageLinkObject: Data = new Data(1); 247 248 build() { 249 Column({ space: 20 }) { 250 Text(`From AppStorage ${this.storageLink}`) 251 .onClick(() => { 252 this.storageLink += 1; 253 }) 254 255 Text(`From LocalStorage ${this.localStorageLink}`) 256 .onClick(() => { 257 this.localStorageLink += 1; 258 }) 259 260 Text(`From AppStorage ${this.storageLinkObject.code}`) 261 .onClick(() => { 262 this.storageLinkObject.code += 1; 263 }) 264 265 Text(`From LocalStorage ${this.localStorageLinkObject.code}`) 266 .onClick(() => { 267 this.localStorageLinkObject.code += 1; 268 }) 269 } 270 } 271} 272``` 273 274### Unrecommended: Using @StorageLink to Implement Event Notification 275 276The two-way synchronization mechanism of @StorageLink and AppStorage is not recommended. This is because the variables in AppStorage may be bound to components on different pages, but the event notifications may not be sent to all these components. In addition, any change to the @StorageLink decorated variables may trigger UI re-rendering, bringing negative impact on the performance. 277 278In the following example, any click event in the **TapImage** component will trigger a change of the **tapIndex** attribute. As @StorageLink establishes a two-way data synchronization with AppStorage, the local change is synchronized to AppStorage. As a result, all custom components owning the **tapIndex** attribute bound to AppStorage are notified of the change. After @Watch observes the change to **tapIndex**, the state variable **tapColor** is updated, and the UI is re-rendered. (Because **tapIndex** is not directly bound to the UI, its change does not directly trigger UI re-rendering.) 279 280To use the preceding mechanism to implement event notification, ensure that variables in AppStorage are not directly bound to the UI and the @Watch decorated function is as simple as possible. (If the @Watch decorated function takes a long time to execute, the UI re-rendering efficiency will be affected.) 281 282 283```ts 284// xxx.ets 285class ViewData { 286 title: string; 287 uri: Resource; 288 color: Color = Color.Black; 289 290 constructor(title: string, uri: Resource) { 291 this.title = title; 292 this.uri = uri 293 } 294} 295 296@Entry 297@Component 298struct Gallery { 299 // 'app.media.icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed. 300 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 301 scroller: Scroller = new Scroller() 302 303 build() { 304 Column() { 305 Grid(this.scroller) { 306 ForEach(this.dataList, (item: ViewData, index?: number) => { 307 GridItem() { 308 TapImage({ 309 uri: item.uri, 310 index: index 311 }) 312 }.aspectRatio(1) 313 314 }, (item: ViewData, index?: number) => { 315 return JSON.stringify(item) + index; 316 }) 317 }.columnsTemplate('1fr 1fr') 318 } 319 320 } 321} 322 323@Component 324export struct TapImage { 325 @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1; 326 @State tapColor: Color = Color.Black; 327 private index: number = 0; 328 private uri: Resource = { 329 id: 0, 330 type: 0, 331 moduleName: "", 332 bundleName: "" 333 }; 334 335 // Check whether the component is selected. 336 onTapIndexChange() { 337 if (this.tapIndex >= 0 && this.index === this.tapIndex) { 338 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, red`) 339 this.tapColor = Color.Red; 340 } else { 341 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, black`) 342 this.tapColor = Color.Black; 343 } 344 } 345 346 build() { 347 Column() { 348 Image(this.uri) 349 .objectFit(ImageFit.Cover) 350 .onClick(() => { 351 this.tapIndex = this.index; 352 }) 353 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 354 } 355 356 } 357} 358``` 359 360Compared with the use of @StorageLink, the use of **emit** implements event notification with less overhead, by allowing you to subscribe to an event and receive an event callback. 361 362> **NOTE** 363> 364> The **emit** API is not available in DevEco Studio Previewer. 365 366 367```ts 368// xxx.ets 369import { emitter } from '@kit.BasicServicesKit'; 370 371let NextID: number = 0; 372 373class ViewData { 374 title: string; 375 uri: Resource; 376 color: Color = Color.Black; 377 id: number; 378 379 constructor(title: string, uri: Resource) { 380 this.title = title; 381 this.uri = uri 382 this.id = NextID++; 383 } 384} 385 386@Entry 387@Component 388struct Gallery { 389 // 'app.media.icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed. 390 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 391 scroller: Scroller = new Scroller() 392 private preIndex: number = -1 393 394 build() { 395 Column() { 396 Grid(this.scroller) { 397 ForEach(this.dataList, (item: ViewData) => { 398 GridItem() { 399 TapImage({ 400 uri: item.uri, 401 index: item.id 402 }) 403 }.aspectRatio(1) 404 .onClick(() => { 405 if (this.preIndex === item.id) { 406 return 407 } 408 let innerEvent: emitter.InnerEvent = { eventId: item.id } 409 // Selected: from black to red 410 let eventData: emitter.EventData = { 411 data: { 412 "colorTag": 1 413 } 414 } 415 emitter.emit(innerEvent, eventData) 416 417 if (this.preIndex != -1) { 418 console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`) 419 let innerEvent: emitter.InnerEvent = { eventId: this.preIndex } 420 // Deselected: from red to black 421 let eventData: emitter.EventData = { 422 data: { 423 "colorTag": 0 424 } 425 } 426 emitter.emit(innerEvent, eventData) 427 } 428 this.preIndex = item.id 429 }) 430 }, (item: ViewData) => JSON.stringify(item)) 431 }.columnsTemplate('1fr 1fr') 432 } 433 434 } 435} 436 437@Component 438export struct TapImage { 439 @State tapColor: Color = Color.Black; 440 private index: number = 0; 441 private uri: Resource = { 442 id: 0, 443 type: 0, 444 moduleName: "", 445 bundleName: "" 446 }; 447 448 onTapIndexChange(colorTag: emitter.EventData) { 449 if (colorTag.data != null) { 450 this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black 451 } 452 } 453 454 aboutToAppear() { 455 // Define the event ID. 456 let innerEvent: emitter.InnerEvent = { eventId: this.index } 457 emitter.on(innerEvent, data => { 458 this.onTapIndexChange(data) 459 }) 460 } 461 462 build() { 463 Column() { 464 Image(this.uri) 465 .objectFit(ImageFit.Cover) 466 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 467 } 468 } 469} 470``` 471 472The preceding notification logic is simple. It can be simplified into a ternary expression as follows: 473 474```ts 475// xxx.ets 476class ViewData { 477 title: string; 478 uri: Resource; 479 color: Color = Color.Black; 480 481 constructor(title: string, uri: Resource) { 482 this.title = title; 483 this.uri = uri 484 } 485} 486 487@Entry 488@Component 489struct Gallery { 490 // 'app.media.icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed. 491 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 492 scroller: Scroller = new Scroller() 493 494 build() { 495 Column() { 496 Grid(this.scroller) { 497 ForEach(this.dataList, (item: ViewData, index?: number) => { 498 GridItem() { 499 TapImage({ 500 uri: item.uri, 501 index: index 502 }) 503 }.aspectRatio(1) 504 505 }, (item: ViewData, index?: number) => { 506 return JSON.stringify(item) + index; 507 }) 508 }.columnsTemplate('1fr 1fr') 509 } 510 511 } 512} 513 514@Component 515export struct TapImage { 516 @StorageLink('tapIndex') tapIndex: number = -1; 517 private index: number = 0; 518 private uri: Resource = { 519 id: 0, 520 type: 0, 521 moduleName: "", 522 bundleName: "" 523 }; 524 525 build() { 526 Column() { 527 Image(this.uri) 528 .objectFit(ImageFit.Cover) 529 .onClick(() => { 530 this.tapIndex = this.index; 531 }) 532 .border({ 533 width: 5, 534 style: BorderStyle.Dotted, 535 color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black 536 }) 537 } 538 } 539} 540``` 541 542 543### Union Type 544 545In the following example, the type of variable **A** is **number | null**, and the type of variable **B** is **number | undefined**. The **Text** components display **null** and **undefined** upon initialization, numbers when clicked, and **null** and **undefined** when clicked again. 546 547```ts 548@Component 549struct StorLink { 550 @StorageLink("LinkA") LinkA: number | null = null; 551 @StorageLink("LinkB") LinkB: number | undefined = undefined; 552 553 build() { 554 Column() { 555 Text("@StorageLink initialization, @StorageLink value") 556 Text(this.LinkA + "").fontSize(20).onClick(() => { 557 this.LinkA ? this.LinkA = null : this.LinkA = 1; 558 }) 559 Text(this.LinkB + "").fontSize(20).onClick(() => { 560 this.LinkB ? this.LinkB = undefined : this.LinkB = 1; 561 }) 562 } 563 .borderWidth(3).borderColor(Color.Red) 564 565 } 566} 567 568@Component 569struct StorProp { 570 @StorageProp("PropA") PropA: number | null = null; 571 @StorageProp("PropB") PropB: number | undefined = undefined; 572 573 build() { 574 Column() { 575 Text("@StorageProp initialization, @StorageProp value") 576 Text(this.PropA + "").fontSize(20).onClick(() => { 577 this.PropA ? this.PropA = null : this.PropA = 1; 578 }) 579 Text(this.PropB + "").fontSize(20).onClick(() => { 580 this.PropB ? this.PropB = undefined : this.PropB = 1; 581 }) 582 } 583 .borderWidth(3).borderColor(Color.Blue) 584 } 585} 586 587@Entry 588@Component 589struct Index { 590 build() { 591 Row() { 592 Column() { 593 StorLink() 594 StorProp() 595 } 596 .width('100%') 597 } 598 .height('100%') 599 } 600} 601``` 602 603 604### Decorating Variables of the Date Type 605 606> **NOTE** 607> 608> AppStorage supports the Set type since API version 12. 609 610In this example, the **selectedDate** variable decorated by @StorageLink is of the Date type. After the button is clicked, the value of **selectedDate** changes, and the UI is re-rendered. 611 612```ts 613@Entry 614@Component 615struct DateSample { 616 @StorageLink("date") selectedDate: Date = new Date('2021-08-08'); 617 618 build() { 619 Column() { 620 Button('set selectedDate to 2023-07-08') 621 .margin(10) 622 .onClick(() => { 623 AppStorage.setOrCreate("date", new Date('2023-07-08')); 624 }) 625 Button('increase the year by 1') 626 .margin(10) 627 .onClick(() => { 628 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 629 }) 630 Button('increase the month by 1') 631 .margin(10) 632 .onClick(() => { 633 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 634 }) 635 Button('increase the day by 1') 636 .margin(10) 637 .onClick(() => { 638 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 639 }) 640 DatePicker({ 641 start: new Date('1970-1-1'), 642 end: new Date('2100-1-1'), 643 selected: $$this.selectedDate 644 }) 645 }.width('100%') 646 } 647} 648``` 649 650 651### Decorating Variables of the Map Type 652 653> **NOTE** 654> 655> AppStorage supports the Map type since API version 12. 656 657In this example, the **message** variable decorated by @StorageLink is of the Map\<number, string\> type. After the button is clicked, the value of **message** changes, and the UI is re-rendered. 658 659```ts 660@Entry 661@Component 662struct MapSample { 663 @StorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 664 665 build() { 666 Row() { 667 Column() { 668 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 669 Text(`${item[0]}`).fontSize(30) 670 Text(`${item[1]}`).fontSize(30) 671 Divider() 672 }) 673 Button('init map').onClick(() => { 674 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 675 }) 676 Button('set new one').onClick(() => { 677 this.message.set(4, "d"); 678 }) 679 Button('clear').onClick(() => { 680 this.message.clear(); 681 }) 682 Button('replace the existing one').onClick(() => { 683 this.message.set(0, "aa"); 684 }) 685 Button('delete the existing one').onClick(() => { 686 AppStorage.get<Map<number, string>>("map")?.delete(0); 687 }) 688 } 689 .width('100%') 690 } 691 .height('100%') 692 } 693} 694``` 695 696 697### Decorating Variables of the Set Type 698 699> **NOTE** 700> 701> AppStorage supports the Set type since API version 12. 702 703In this example, the **memberSet** variable decorated by @StorageLink is of the Set\<number\> type. After the button is clicked, the value of **memberSet** changes, and the UI is re-rendered. 704 705```ts 706@Entry 707@Component 708struct SetSample { 709 @StorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 710 711 build() { 712 Row() { 713 Column() { 714 ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => { 715 Text(`${item[0]}`) 716 .fontSize(30) 717 Divider() 718 }) 719 Button('init set') 720 .onClick(() => { 721 this.memberSet = new Set([0, 1, 2, 3, 4]); 722 }) 723 Button('set new one') 724 .onClick(() => { 725 AppStorage.get<Set<number>>("set")?.add(5); 726 }) 727 Button('clear') 728 .onClick(() => { 729 this.memberSet.clear(); 730 }) 731 Button('delete the first one') 732 .onClick(() => { 733 this.memberSet.delete(0); 734 }) 735 } 736 .width('100%') 737 } 738 .height('100%') 739 } 740} 741``` 742 743## FAQs 744 745### Value Changed by \@StorageProp Locally Fails to Update through AppStorage 746 747```ts 748AppStorage.setOrCreate('PropA', false); 749 750@Entry 751@Component 752struct Index { 753 @StorageProp('PropA') @Watch('onChange') propA: boolean = false; 754 755 onChange() { 756 console.log(`propA change`); 757 } 758 759 aboutToAppear(): void { 760 this.propA = true; 761 } 762 763 build() { 764 Column() { 765 Text(`${this.propA}`) 766 Button('change') 767 .onClick(() => { 768 AppStorage.setOrCreate('PropA', false); 769 console.log(`PropA: ${this.propA}`); 770 }) 771 } 772 } 773} 774``` 775 776In the preceding example, the value of **PropA** has been changed to **true** locally before the click event, but the value stored in **AppStorage** is still **false**. When the click event attempts to update the value of **PropA** to **false** through the **setOrCreate** API, the value of @StorageProp remains **true** because the local value and stored value of **PropA** are the same. 777 778To synchronize the two values, use either of the following methods: 779(1) Change \@StorageProp to \@StorageLink. 780(2) Use **AppStorage.setOrCreate('PropA', true)** to change the value locally. 781