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