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![en-us_image_0000001552978157](figures/en-us_image_0000001552978157.png)
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![en-us_image_0000001501938718](figures/en-us_image_0000001501938718.png)
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