• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
10PersistentStorage provides capability for persisting the state variables. However, the persistence and UI reading capabilities depend on AppStorage. Before reading this topic, you are advised to read [AppStorage](./arkts-appstorage.md) and [PersistentStorage API reference](../reference/apis-arkui/arkui-ts/ts-state-management.md#persistentstorage).
11
12## Overview
13
14PersistentStorage 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.
15
16PersistentStorage 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.
17
18The data storage path of PersistentStorage is at the module level. That is, the data copy is stored in the persistent file of the corresponding module when the module calls PersistentStorage. If multiple modules use the same key, the data is copied from and stored in the module that uses PersistentStorage first.
19
20The storage path of PersistentStorage, determined when the first ability of the application is started, is the module to which the ability belongs. If an ability calls PersistentStorage and can be started by different modules, the number of data copies is the same as the number of startup modes of the ability.
21
22PersistentStorage is coupled with AppStorage in terms of functions, and errors may occur when using data in different modules. Therefore, you are advised to use the **globalConnect** API of PersistenceV2 to replace the **persistProp** API of PersistentStorage. For details about how to migrate data from PersistentStorage to PersistenceV2, see [PersistentStorage->PersistenceV2](arkts-v1-v2-migration.md#persistentstorage-persistencev2). For details about PersistenceV2, see [PersistenceV2: Persisting Application State](arkts-new-persistencev2.md).
23
24## Constraints
25
26PersistentStorage accepts the following types and values:
27
28- Primitive types such as number, string, boolean, and enum.
29- Objects that can be reconstructed by **JSON.stringify()** and **JSON.parse()**, but member methods of the objects are not supported.
30- 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).
31- 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).
32- 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).
33- **undefined** and **null** since API version 12.
34- [Union types](#union-types) since API version 12.
35
36PersistentStorage does not accept the following types and values:
37
38- Nested objects (object arrays and object attributes), because the framework cannot detect the value changes of nested objects (including arrays) in AppStorage.
39
40Data persistence is a time-consuming operation. As such, avoid the following situations whenever possible:
41
42- Persistence of large data sets
43
44- Persistence of variables that change frequently
45
46It 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.
47
48PersistentStorage 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).
49
50```ts
51// EntryAbility.ets
52onWindowStageCreate(windowStage: window.WindowStage): void {
53  windowStage.loadContent('pages/Index', (err) => {
54    if (err.code) {
55      return;
56    }
57    PersistentStorage.persistProp('aProp', 47);
58  });
59}
60```
61
62## Use Scenarios
63
64
65### Accessing PersistentStorage Initialized Attribute from AppStorage
66
671. Initialize the PersistentStorage instance.
68
69   ```ts
70   PersistentStorage.persistProp('aProp', 47);
71   ```
72
732. Obtain the corresponding attribute from AppStorage.
74
75   ```ts
76   AppStorage.get<number>('aProp'); // returns 47
77   ```
78
79   Alternatively, apply local definition within the component:
80
81
82   ```ts
83   @StorageLink('aProp') aProp: number = 48;
84   ```
85
86The complete code is as follows:
87
88
89```ts
90PersistentStorage.persistProp('aProp', 47);
91
92@Entry
93@Component
94struct Index {
95  @State message: string = 'Hello World';
96  @StorageLink('aProp') aProp: number = 48;
97
98  build() {
99    Row() {
100      Column() {
101        Text(this.message)
102        // The current result is saved when the application exits. After the restart, the last saved result is displayed.
103        Text(`${this.aProp}`)
104          .onClick(() => {
105            this.aProp += 1;
106          })
107      }
108    }
109  }
110}
111```
112
113- First running after fresh application installation:
114  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.
115  2. A search for the attribute **aProp** in AppStorage still returns no result.
116  3. Create the **aProp** attribute of the number type in AppStorge and initialize it with the value **47**.
117  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.
118  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.
119
120  **Figure 1** PersistProp initialization process
121
122![en-us_image_0000001553348833](figures/en-us_image_0000001553348833.png)
123
124- After a click event is triggered:
125  1. The state variable **\@StorageLink('aProp') aProp** is updated, triggering the **\<Text>** component to be re-rendered.
126  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.
127  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.)
128  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.
129
130- Subsequent application running:
131  1. **PersistentStorage.persistProp('aProp', 47)** is called. A search for the **aProp** attribute in PersistentStorage succeeds.
132  2. The attribute is added to AppStorage with the value found in PersistentStorage.
133  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.
134
135
136### Accessing an Attribute in AppStorage Before PersistentStorage
137
138This 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:
139
140
141```ts
142let aProp = AppStorage.setOrCreate('aProp', 47);
143PersistentStorage.persistProp('aProp', 48);
144```
145
146**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.
147
148PersistentStorage.persistProp('aProp', 48): An attribute with the name **aProp** and value **47** – set through the API in AppStorage – is found in PersistentStorage.
149
150### Accessing an Attribute in AppStorage After PersistentStorage
151
152If 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.
153
154```ts
155PersistentStorage.persistProp('aProp', 48);
156if (AppStorage.get('aProp') > 50) {
157    // If the value stored in PersistentStorage exceeds 50, set the value to 47.
158    AppStorage.setOrCreate('aProp',47);
159}
160```
161
162After 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.
163
164
165### Union Types
166
167PersistentStorage 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.
168
169```ts
170PersistentStorage.persistProp("P", undefined);
171
172@Entry
173@Component
174struct TestCase6 {
175  @StorageLink("P") p: number | undefined | null = 10;
176
177  build() {
178    Row() {
179      Column() {
180        Text(this.p + "")
181          .fontSize(50)
182          .fontWeight(FontWeight.Bold)
183        Button("changeToNumber").onClick(() => {
184          this.p = 10;
185        })
186        Button("changeTo undefined").onClick(() => {
187          this.p = undefined;
188        })
189        Button("changeTo null").onClick(() => {
190          this.p = null;
191        })
192      }
193      .width('100%')
194    }
195    .height('100%')
196  }
197}
198```
199
200
201### Decorating Variables of the Date Type
202
203In 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.
204
205```ts
206PersistentStorage.persistProp("persistedDate", new Date());
207
208@Entry
209@Component
210struct PersistedDate {
211  @StorageLink("persistedDate") persistedDate: Date = new Date();
212
213  updateDate() {
214    this.persistedDate = new Date();
215  }
216
217  build() {
218    List() {
219      ListItem() {
220        Column() {
221          Text(`Persisted Date is ${this.persistedDate.toString()}`)
222            .margin(20)
223
224          Text(`Persisted Date year is ${this.persistedDate.getFullYear()}`)
225            .margin(20)
226
227          Text(`Persisted Date hours is ${this.persistedDate.getHours()}`)
228            .margin(20)
229
230          Text(`Persisted Date minutes is ${this.persistedDate.getMinutes()}`)
231            .margin(20)
232
233          Text(`Persisted Date time is ${this.persistedDate.toLocaleTimeString()}`)
234            .margin(20)
235
236          Button() {
237            Text('Update Date')
238              .fontSize(25)
239              .fontWeight(FontWeight.Bold)
240              .fontColor(Color.White)
241          }
242          .type(ButtonType.Capsule)
243          .margin({
244            top: 20
245          })
246          .backgroundColor('#0D9FFB')
247          .width('60%')
248          .height('5%')
249          .onClick(() => {
250            this.updateDate();
251          })
252
253        }.width('100%')
254      }
255    }
256  }
257}
258```
259
260### Decorating Variables of the Map Type
261
262In 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.
263
264```ts
265PersistentStorage.persistProp("persistedMapString", new Map<number, string>([]));
266
267@Entry
268@Component
269struct PersistedMap {
270  @StorageLink("persistedMapString") persistedMapString: Map<number, string> = new Map<number, string>([]);
271
272  persistMapString() {
273    this.persistedMapString = new Map<number, string>([[3, "one"], [6, "two"], [9, "three"]]);
274  }
275
276  build() {
277    List() {
278      ListItem() {
279        Column() {
280          Text(`Persisted Map String is `)
281            .margin(20)
282          ForEach(Array.from(this.persistedMapString.entries()), (item: [number, string]) => {
283            Text(`${item[0]} ${item[1]}`)
284          })
285
286          Button() {
287            Text('Persist Map String')
288              .fontSize(25)
289              .fontWeight(FontWeight.Bold)
290              .fontColor(Color.White)
291          }
292          .type(ButtonType.Capsule)
293          .margin({
294            top: 20
295          })
296          .backgroundColor('#0D9FFB')
297          .width('60%')
298          .height('5%')
299          .onClick(() => {
300            this.persistMapString();
301          })
302
303        }.width('100%')
304      }
305    }
306  }
307}
308```
309
310### Decorating Variables of the Set Type
311
312In 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.
313
314```ts
315PersistentStorage.persistProp("persistedSet", new Set<number>([]));
316
317@Entry
318@Component
319struct PersistedSet {
320  @StorageLink("persistedSet") persistedSet: Set<number> = new Set<number>([]);
321
322  persistSet() {
323    this.persistedSet = new Set<number>([33, 1, 3]);
324  }
325
326  clearSet() {
327    this.persistedSet.clear();
328  }
329
330  build() {
331    List() {
332      ListItem() {
333        Column() {
334          Text(`Persisted Set is `)
335            .margin(20)
336          ForEach(Array.from(this.persistedSet.entries()), (item: [number, string]) => {
337            Text(`${item[1]}`)
338          })
339
340          Button() {
341            Text('Persist Set')
342              .fontSize(25)
343              .fontWeight(FontWeight.Bold)
344              .fontColor(Color.White)
345          }
346          .type(ButtonType.Capsule)
347          .margin({
348            top: 20
349          })
350          .backgroundColor('#0D9FFB')
351          .width('60%')
352          .height('5%')
353          .onClick(() => {
354            this.persistSet();
355          })
356
357          Button() {
358            Text('Persist Clear')
359              .fontSize(25)
360              .fontWeight(FontWeight.Bold)
361              .fontColor(Color.White)
362          }
363          .type(ButtonType.Capsule)
364          .margin({
365            top: 20
366          })
367          .backgroundColor('#0D9FFB')
368          .width('60%')
369          .height('5%')
370          .onClick(() => {
371            this.clearSet();
372          })
373
374        }
375        .width('100%')
376      }
377    }
378  }
379}
380```
381