1# PersistentStorage: Persisting Application State
2
3
4During application development, you may want selected attributes to persist even when the application is closed. In this case, you'll need PersistentStorage.
5
6
7PersistentStorage is an optional singleton object within an application. Its purpose is to persist selected AppStorage attributes so that their values are the same upon application re-start as they were when the application was closed.
8
9
10## Overview
11
12PersistentStorage retains the selected AppStorage attributes on the device. The application uses the API to determine which AppStorage attributes should be persisted with PersistentStorage. The UI and business logic do not directly access attributes in PersistentStorage. All attribute access is to AppStorage. Changes in AppStorage are automatically synchronized to PersistentStorage.
13
14PersistentStorage creates a two-way synchronization with attributes in AppStorage. A frequently used API function is to access AppStorage through PersistentStorage. Additional API functions include managing persisted attributes. The business logic always obtains or sets attributes through AppStorage.
15
16## Constraints
17
18PersistentStorage accepts the following types and values:
19
20- Primitive types such as number, string, boolean, and enum.
21- Objects that can be reconstructed by **JSON.stringify()** and **JSON.parse()**, but member methods of the objects are not supported.
22- Map type since API version 12: The overall value changes of the Map instance can be observed; you can call the **set**, **clear**, and **delete** APIs to update the instance; the updated value is persisted. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type).
23- Set type since API version 12: The overall value changes of the Set instance can be observed; you can call the **add**, **clear**, and **delete** APIs to update the instance; the updated value is persisted. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type).
24- Date type since API version 12: The overall value changes of the Date instance can be observed; you can call the following APIs to update the Date properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. the updated value is persisted. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type).
25- **undefined** and **null** since API version 12.
26- [Union types](#union-types) since API version 12.
27
28PersistentStorage does not accept the following types and values:
29
30- Nested objects (object arrays and object attributes), because the framework cannot detect the value changes of nested objects (including arrays) in AppStorage.
31
32Data persistence is a time-consuming operation. As such, avoid the following situations whenever possible:
33
34- Persistence of large data sets
35
36- Persistence of variables that change frequently
37
38It is recommended that the persistent variables of PersistentStorage be less than 2 KB. As PersistentStorage flushes data synchronously, a large amount of persistent data may result in simultaneous time-consuming read and write operations in the UI thread, affecting UI rendering performance. If you need to store a large amount of data, consider using the database API.
39
40PersistentStorage is associated with UI instances. Data persistence can succeed only when a UI instance has been initialized (that is, when the callback passed in by [loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9-2) is called).
41
42```ts
43// EntryAbility.ets
44onWindowStageCreate(windowStage: window.WindowStage): void {
45  windowStage.loadContent('pages/Index', (err) => {
46    if (err.code) {
47      return;
48    }
49    PersistentStorage.persistProp('aProp', 47);
50  });
51}
52```
53
54## Use Scenarios
55
56
57### Accessing PersistentStorage Initialized Attribute from AppStorage
58
591. Initialize the PersistentStorage instance.
60
61   ```ts
62   PersistentStorage.persistProp('aProp', 47);
63   ```
64
652. Obtain the corresponding attribute from AppStorage.
66
67   ```ts
68   AppStorage.get<number>('aProp'); // returns 47
69   ```
70
71   Alternatively, apply local definition within the component:
72
73
74      ```ts
75      @StorageLink('aProp') aProp: number = 48;
76      ```
77
78The complete code is as follows:
79
80
81```ts
82PersistentStorage.persistProp('aProp', 47);
83
84@Entry
85@Component
86struct Index {
87  @State message: string = 'Hello World'
88  @StorageLink('aProp') aProp: number = 48
89
90  build() {
91    Row() {
92      Column() {
93        Text(this.message)
94        // The current result is saved when the application exits. After the restart, the last saved result is displayed.
95        Text(`${this.aProp}`)
96          .onClick(() => {
97            this.aProp += 1;
98          })
99      }
100    }
101  }
102}
103```
104
105- First running after fresh application installation:
106  1. **persistProp** is called to initialize PersistentStorage. A search for the **aProp** attribute in PersistentStorage returns no result, because the application has just been installed.
107  2. A search for the attribute **aProp** in AppStorage still returns no result.
108  3. Create the **aProp** attribute of the number type in AppStorge and initialize it with the value 47.
109  4. PersistentStorage writes the **aProp** attribute and its value **47** to the local device. The value of **aProp** in AppStorage and its subsequent changes are persisted.
110  5. In the **Index** component, create the state variable **\@StorageLink('aProp') aProp**, which creates a two-way synchronization with the **aProp** attribute in AppStorage. During the creation, the search in AppStorage for the **aProp** attribute is successful, and therefore, the state variable is initialized with the value **47** found in AppStorage.
111
112  **Figure 1** PersistProp initialization process
113
114![en-us_image_0000001553348833](figures/en-us_image_0000001553348833.png)
115
116- After a click event is triggered:
117  1. The state variable **\@StorageLink('aProp') aProp** is updated, triggering the **Text** component to be re-rendered.
118  2. The two-way synchronization between the \@StorageLink decorated variable and AppStorage results in the change of the **\@StorageLink('aProp') aProp** being synchronized back to AppStorage.
119  3. The change of the **aProp** attribute in AppStorage triggers any other one-way or two-way bound variables to be updated. (In this example, there are no such other variables.)
120  4. Because the attribute corresponding to **aProp** has been persisted, the change of the **aProp** attribute in AppStorage triggers PersistentStorage to write the attribute and its new value to the device.
121
122- Subsequent application running:
123  1. **PersistentStorage.persistProp('aProp', 47)** is called. A search for the **aProp** attribute in PersistentStorage succeeds.
124  2. The attribute is added to AppStorage with the value found in PersistentStorage.
125  3. In the **Index** component, the value of the @StorageLink decorated **aProp** attribute is the value written by PersistentStorage to AppStorage, that is, the value stored when the application was closed last time.
126
127
128### Accessing an Attribute in AppStorage Before PersistentStorage
129
130This example is an incorrect use. It is incorrect to use the API to access the attributes in AppStorage before calling **PersistentStorage.persistProp** or **persistProps**, because such a call sequence will result in loss of the attribute values used in the previous application run:
131
132
133```ts
134let aProp = AppStorage.setOrCreate('aProp', 47);
135PersistentStorage.persistProp('aProp', 48);
136```
137
138**AppStorage.setOrCreate('aProp', 47)**: The **aProp** attribute of the number type is created in AppStorage, and its value is set to the specified default value **47**. **aProp** is a persisted attribute. Therefore, it is written back to PersistentStorage, and the value stored in PersistentStorage from the previous run is lost.
139
140PersistentStorage.persistProp('aProp', 48): An attribute with the name **aProp** and value **47** – set through the API in AppStorage – is found in PersistentStorage.
141
142### Accessing an Attribute in AppStorage After PersistentStorage
143
144If you do not want to overwrite the values saved in PersistentStorage during the previous application run, make sure any access to attributes in AppStorage is made after a call to a PersistentStorage API.
145
146```ts
147PersistentStorage.persistProp('aProp', 48);
148if (AppStorage.get('aProp') > 50) {
149    // If the value stored in PersistentStorage exceeds 50, set the value to 47.
150    AppStorage.setOrCreate('aProp',47);
151}
152```
153
154After reading the data stored in PersistentStorage, the sample code checks whether the value of **aProp** is greater than 50 and, if it is, sets **aProp** to **47** through an API in AppStorage.
155
156
157### Union Types
158
159PersistentStorage supports union types, **undefined**, and **null**. In the following example, the **persistProp** API is used to initialize **"P"** to **undefined**. **@StorageLink("P")** is used to bind variable **p** of the **number | undefined | null** type to the component. After the button is clicked, the value of **P** changes, and the UI is re-rendered. In addition, the value of **P** is persisted.
160
161```ts
162PersistentStorage.persistProp("P", undefined);
163
164@Entry
165@Component
166struct TestCase6 {
167  @StorageLink("P") p: number | undefined | null = 10;
168
169  build() {
170    Row() {
171      Column() {
172        Text(this.p + "")
173          .fontSize(50)
174          .fontWeight(FontWeight.Bold)
175        Button("changeToNumber").onClick(() => {
176          this.p = 10;
177        })
178        Button("changeTo undefined").onClick(() => {
179          this.p = undefined;
180        })
181        Button("changeTo null").onClick(() => {
182          this.p = null;
183        })
184      }
185      .width('100%')
186    }
187    .height('100%')
188  }
189}
190```
191
192
193### Decorating Variables of the Date Type
194
195In this example, the **persistedDate** variable decorated by @StorageLink is of the Date type. After the button is clicked, the value of **persistedDate** changes, and the UI is re-rendered. In addition, the value of **persistedDate** is persisted.
196
197```ts
198PersistentStorage.persistProp("persistedDate", new Date());
199
200@Entry
201@Component
202struct PersistedDate {
203  @StorageLink("persistedDate") persistedDate: Date = new Date();
204
205  updateDate() {
206    this.persistedDate = new Date();
207  }
208
209  build() {
210    List() {
211      ListItem() {
212        Column() {
213          Text(`Persisted Date is ${this.persistedDate.toString()}`)
214            .margin(20)
215
216          Text(`Persisted Date month is ${this.persistedDate.getMonth()}`)
217            .margin(20)
218
219          Text(`Persisted Date day is ${this.persistedDate.getDay()}`)
220            .margin(20)
221
222          Text(`Persisted Date time is ${this.persistedDate.toLocaleTimeString()}`)
223            .margin(20)
224
225          Button() {
226            Text('Update Date')
227              .fontSize(25)
228              .fontWeight(FontWeight.Bold)
229              .fontColor(Color.White)
230          }
231          .type(ButtonType.Capsule)
232          .margin({
233            top: 20
234          })
235          .backgroundColor('#0D9FFB')
236          .width('60%')
237          .height('5%')
238          .onClick(() => {
239            this.updateDate();
240          })
241
242        }.width('100%')
243      }
244    }
245  }
246}
247```
248
249### Decorating Variables of the Map Type
250
251In this example, the **persistedMapString** variable decorated by @StorageLink is of the Map\<number, string\> type. After the button is clicked, the value of **persistedMapString** changes, and the UI is re-rendered. In addition, the value of **persistedMapString** is persisted.
252
253```ts
254PersistentStorage.persistProp("persistedMapString", new Map<number, string>([]));
255
256@Entry
257@Component
258struct PersistedMap {
259  @StorageLink("persistedMapString") persistedMapString: Map<number, string> = new Map<number, string>([]);
260
261  persistMapString() {
262    this.persistedMapString = new Map<number, string>([[3, "one"], [6, "two"], [9, "three"]]);
263  }
264
265  build() {
266    List() {
267      ListItem() {
268        Column() {
269          Text(`Persisted Map String is `)
270            .margin(20)
271          ForEach(Array.from(this.persistedMapString.entries()), (item: [number, string]) => {
272            Text(`${item[0]} ${item[1]}`)
273          })
274
275          Button() {
276            Text('Persist Map String')
277              .fontSize(25)
278              .fontWeight(FontWeight.Bold)
279              .fontColor(Color.White)
280          }
281          .type(ButtonType.Capsule)
282          .margin({
283            top: 20
284          })
285          .backgroundColor('#0D9FFB')
286          .width('60%')
287          .height('5%')
288          .onClick(() => {
289            this.persistMapString();
290          })
291
292        }.width('100%')
293      }
294    }
295  }
296}
297```
298
299### Decorating Variables of the Set Type
300
301In this example, the **persistedSet** variable decorated by @StorageLink is of the Set\<number\> type. After the button is clicked, the value of **persistedSet** changes, and the UI is re-rendered. In addition, the value of **persistedSet** is persisted.
302
303```ts
304PersistentStorage.persistProp("persistedSet", new Set<number>([]));
305
306@Entry
307@Component
308struct PersistedSet {
309  @StorageLink("persistedSet") persistedSet: Set<number> = new Set<number>([]);
310
311  persistSet() {
312    this.persistedSet = new Set<number>([33, 1, 3]);
313  }
314
315  clearSet() {
316    this.persistedSet.clear();
317  }
318
319  build() {
320    List() {
321      ListItem() {
322        Column() {
323          Text(`Persisted Set is `)
324            .margin(20)
325          ForEach(Array.from(this.persistedSet.entries()), (item: [number, string]) => {
326            Text(`${item[1]}`)
327          })
328
329          Button() {
330            Text('Persist Set')
331              .fontSize(25)
332              .fontWeight(FontWeight.Bold)
333              .fontColor(Color.White)
334          }
335          .type(ButtonType.Capsule)
336          .margin({
337            top: 20
338          })
339          .backgroundColor('#0D9FFB')
340          .width('60%')
341          .height('5%')
342          .onClick(() => {
343            this.persistSet();
344          })
345
346          Button() {
347            Text('Persist Clear')
348              .fontSize(25)
349              .fontWeight(FontWeight.Bold)
350              .fontColor(Color.White)
351          }
352          .type(ButtonType.Capsule)
353          .margin({
354            top: 20
355          })
356          .backgroundColor('#0D9FFB')
357          .width('60%')
358          .height('5%')
359          .onClick(() => {
360            this.clearSet();
361          })
362
363        }
364        .width('100%')
365      }
366    }
367  }
368}
369```
370