• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![en-us_image_0000001552978157](figures/en-us_image_0000001552978157.png)
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![en-us_image_0000001501938718](figures/en-us_image_0000001501938718.png)
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