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