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