1# Cross-Device Synchronization of Distributed Data Objects 2 3 4## When to Use 5 6The traditional implementation of data synchronization between devices involves heavy workload. You need to design the message processing logic for setting up a communication link, sending, receiving, and processing messages, and resolving data conflicts, as well as retry mechanism upon errors. In addition, the debugging complexity increases with the number of devices. 7 8The device status, message sending progress, and data transmitted are variables. If these variables support global access, they can be accessed as local variables by difference devices. This simplifies data synchronization across devices. 9 10The distributed data object (**distributedDataObject**) module implements global access to variables. It provides basic data object management capabilities, including creating, querying, deleting, and modifying in-memory objects and subscribing to data or status changes. It also provides distributed capabilities. OpenHarmony provides easy-to-use JS APIs for distributed application scenarios. With these APIs, you can easily implement data collaboration for an application across devices and listening for status and data changes between devices. The **distributedDataObject** module implements data object collaboration for the same application across multiple devices that form a Super Device. It greatly reduces the development workloads compared with the traditional implementation. 11 12 13## Basic Concepts 14 15- Distributed in-memory database<br> 16 The distributed in-memory database caches data in the memory so that applications can quickly access data without persisting data. If the database is closed, the data is not retained. 17 18- Distributed data object<br> 19 A distributed data object is an encapsulation of the JS object type. Each distributed data object instance creates a data table in the in-memory database. The in-memory databases created for different applications are isolated from each other. Reading and writing a distributed data object are mapped to the **get** and **put** operations in the corresponding database, respectively. 20 21 The distributed data object has the following states in its lifecycle: 22 23 - **Uninitialized**: The distributed data object is not instantiated or is destroyed. 24 - **Local**: A data table is created, but the data cannot be synchronized. 25 - **Distributed**: A data table is created, and data can be synchronized (there are at least two online devices with the same session ID). If a device is offline or the session ID is empty, the distributed data object changes to the local state. 26 27 28## Working Principles 29 30**Figure 1** Working mechanism 31 32 33 34The distributed data objects are encapsulated JS objects in distributed in-memory databases, and can be operated in the same way as local variables. The system automatically implements data synchronization across devices. 35 36 37### Encapsulation and Storage of JS Objects 38 39- An in-memory database is created for each distributed data object instance and identified by a session ID (**SessionId**). The in-memory databases created for different applications are isolated from each other. 40 41- When a distributed data object is instantiated, all properties of the object are traversed recursively. **Object.defineProperty** is used to define the **set()** and **get()** methods for all properties. The **set()** and **get()** methods correspond to the **put** and **get** operations of a record in the database, respectively. **Key** specifies the property name, and **Value** specifies the property value. 42 43- When a distributed data object is read or written, the **get()** or **set()** method is automatically called to perform the related operation on data in the database. 44 45**Table 1** Correspondence between a distributed data object and a distributed database 46 47| Distributed Data Object Instance| Object Instance| Property Name| Property Value| 48| -------- | -------- | -------- | -------- | 49| Distributed in-memory database| Database identified by **sessionID**| Key of a record in the database| Value of a record in the database| 50 51 52### Cross-Device Synchronization and Data Change Notification 53 54One of the most important functions of distributed data objects is to implement data synchronization between objects. Distributed data objects are created locally for the devices on a trusted network. If the distributed data objects on different devices are set with the same **sessionID**, data can be synchronized between them. 55 56As shown in the following figure, distributed data object 1 of device A and distributed data object 1 of device B are set with the same session ID **session1**, and synchronization relationship of session 1 is established between the two objects. 57 58 **Figure 2** Object synchronization relationship 59 60 61 62For each device, only one distributed data object can be added to a synchronization relationship. As shown in the preceding figure, distributed data object 2 of device A cannot be added to session 1 because distributed data object 1 of device A has been added to session 1. 63 64After the synchronization relationship is established, each session has a copy of shared object data. The distributed data objects added to a session support the following operations: 65 66- Reading or modifying the data in the session. 67 68- Listening for data changes made by other devices. 69 70- Listening for status changes, such as the addition and removal of other devices. 71 72 73### Minimum Synchronization Unit 74 75Property is the minimum unit to synchronize in distributed data objects. For example, object 1 in the following figure has three properties: name, age, and parents. If one of the properties is changed, only the changed property needs to be synchronized. 76 77**Figure 3** Synchronization of distributed data objects 78 79 80 81 82 83### Persistence of Distributed Data Objects 84 85Distributed data objects run in the process space of applications. After the data of a distributed data object is persisted in the distributed database, the data will not be lost after the application exits. 86 87You need to persist distributed data objects in the following scenarios: 88 89- Enable an application to retrieve the exact same data after it starts again. In this case, you need to persist the distributed data object (for example, object 1 with session ID 1). After the application starts again, create a distributed data object (for example, object 2) and set the session ID to 1. Then, the application can retrieve the data of object 1. 90 91- Enable an application started on another device to retrieve the exact same data. In this case, you need to persist the distributed data object (for example, object 1 with session ID 1) on device A and synchronize the data to device B. Then, create a distributed data object (for example, object 2) and set the session ID to 1. When the application is started on device B, it can retrieve the same application data used on device A before the application is closed. 92 93### Asset Synchronization Mechanism 94 95In a distributed object, [asset](../reference/apis-arkdata/js-apis-data-commonType.md#asset) is used to describe a local entity asset file. When the distributed object is synchronized across devices, the file is also synchronized to other devices with it. Currently, only asset is supported. The type [assets](../reference/apis-arkdata/js-apis-data-commonType.md#assets) is not supported. To synchronize multiple assets, use each asset as a root property of the distributed object. 96 97### Resolution of Joint Asset Conflicts 98 99When an asset in a distributed object and an asset in an RDB store point to the same entity asset file, that is, the URIs of the two assets are the same, a conflict occurs. Such assets are called joint assets. To resolve the conflict of joint assets, bind the asset and the RDB store. The binding is automatically released when the application exits the session. 100 101## Constraints 102 103- Only the data of the same application can be synchronized across devices, that is, the devices must have the same **bundleName**. 104 105- Data can be synchronized for the distributed data objects with the same session ID. 106 107- Each distributed data object occupies 100 KB to 150 KB of memory. Therefore, you are advised not to create too many distributed data objects. 108 109- The maximum size of a distributed data object is 500 KB. 110 111- If data of 1 KB data is modified on device A, device B can complete data update within 50 ms after receiving a data change notification. 112 113- A maximum of 16 distributed data object instances can be created for an application. 114 115- For the sake of performance and user experience, the maximum number of devices for data collaboration is 3. 116 117- For the distributed data object of the complex type, only the root property can be modified. The subordinate properties cannot be modified. In [asset synchronization mechanism](#asset-synchronization-mechanism), the data of the asset type must support modification of its lower-level properties. 118 119- Currently, only JS APIs are supported. 120 121## Available APIs 122 123Most of the APIs for cross-device synchronization of distributed data objects are executed asynchronously in callback or promise mode. The following table uses the callback-based APIs as an example. For more information about the APIs, see [Distributed Data Object](../reference/apis-arkdata/js-apis-data-distributedobject.md). 124 125 126 127| API| Description| 128| -------- | -------- | 129| create(context: Context, source: object): DataObject | Creates a distributed data object instance.| 130| genSessionId(): string | Generates a session ID for distributed data objects.| 131| setSessionId(sessionId: string, callback: AsyncCallback<void>): void | Sets a session ID for data synchronization. Automatic synchronization is performed for devices with the same session ID on a trusted network.| 132| setSessionId(callback: AsyncCallback<void>): void | Exits all sessions.| 133| on(type: 'change', callback: (sessionId: string, fields: Array<string>) => void): void | Subscribes to data changes of the distributed data object.| 134| off(type: 'change', callback?: (sessionId: string, fields: Array<string>) => void): void | Unsubscribes from data changes of the distributed data object.| 135| on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' \| 'offline' ) => void): void | Subscribes to status changes of the distributed data object.| 136| off(type: 'status', callback?: (sessionId: string, networkId: string, status: 'online' \|'offline' ) => void): void | Unsubscribes from status changes of the distributed data object.| 137| save(deviceId: string, callback: AsyncCallback<SaveSuccessResponse>): void | Saves a distributed data object.| 138| revokeSave(callback: AsyncCallback<RevokeSaveSuccessResponse>): void | Revokes the saving of the distributed data object.| 139| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback<void>): void | Binds an asset and its RDB store.| 140 141 142## How to Develop 143 144### Data Synchronization Across Devices 145 146The following example demonstrates how to implement synchronization of distributed data objects. 147 1481. Import the **@ohos.data.distributedDataObject** module. 149 150 ```ts 151 import distributedDataObject from '@ohos.data.distributedDataObject'; 152 ``` 153 1542. Apply for permissions. 155 156 1. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 157 2. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 158 1593. Create a distributed data object instance. 160 161 Stage model: 162 163 ```ts 164 // Import the module. 165 import distributedDataObject from '@ohos.data.distributedDataObject'; 166 import UIAbility from '@ohos.app.ability.UIAbility'; 167 import { BusinessError } from '@ohos.base'; 168 import window from '@ohos.window'; 169 170 class ParentObject { 171 mother: string 172 father: string 173 174 constructor(mother: string, father: string) { 175 this.mother = mother 176 this.father = father 177 } 178 } 179 class SourceObject { 180 name: string | undefined 181 age: number | undefined 182 isVis: boolean | undefined 183 parent: Object | undefined 184 185 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) { 186 this.name = name 187 this.age = age 188 this.isVis = isVis 189 this.parent = parent 190 } 191 } 192 193 class EntryAbility extends UIAbility { 194 onWindowStageCreate(windowStage: window.WindowStage) { 195 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 196 let source: SourceObject = new SourceObject("jack", 18, false, parentSource); 197 let localObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, source); 198 } 199 } 200 ``` 201 202 FA model: 203 204 ```ts 205 // Import the module. 206 import distributedDataObject from '@ohos.data.distributedDataObject'; 207 import featureAbility from '@ohos.ability.featureAbility'; 208 // Obtain the context. 209 let context = featureAbility.getContext(); 210 class ParentObject { 211 mother: string 212 father: string 213 constructor(mother: string, father: string) { 214 this.mother = mother 215 this.father = father 216 } 217 } 218 class SourceObject { 219 name: string | undefined 220 age: number | undefined 221 isVis: boolean | undefined 222 parent: ParentObject | undefined 223 constructor(name: string | undefined, age: number | undefined, isVis: boolean | undefined, parent: ParentObject | undefined) { 224 this.name = name 225 this.age = age 226 this.isVis = isVis 227 this.parent = parent 228 } 229 } 230 let parentSource: ParentObject = new ParentObject('jack mom', 'jack Dad'); 231 let source: SourceObject = new SourceObject("jack", 18, false, parentSource); 232 // Create a distributed data object, which has properties of the string, number, boolean, and object types. 233 let localObject: distributedDataObject.DataObject = distributedDataObject.create(context, source); 234 ``` 235 2364. Set the same session ID for the distributed data objects for data synchronization. The data objects in the synchronization network include the local and remote objects. 237 238 ```ts 239 // Set a session ID, for example, 123456, for device 1. 240 let sessionId: string = '123456'; 241 242 localObject.setSessionId(sessionId); 243 244 // Set the same session ID for device 2. 245 246 // Create a distributed data object, which has properties of the string, number, boolean, and object types. 247 let remoteSource: SourceObject = new SourceObject(undefined, undefined, undefined, undefined); 248 let remoteObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, remoteSource); 249 // After receiving the message indicating the device goes online, the remote object synchronizes data. That is, name is changed to jack and age is changed to 18. 250 remoteObject.setSessionId(sessionId); 251 ``` 252 2535. Observe data changes of a distributed data object. You can subscribe to data changes of the remote object. When the data in the remote object changes, a callback will be invoked to return a data change event. 254 255 ```ts 256 localObject.on("change", (sessionId: string, fields: Array<string>) => { 257 console.info("change" + sessionId); 258 if (fields != null && fields != undefined) { 259 for (let index: number = 0; index < fields.length; index++) { 260 console.info(`The element ${localObject[fields[index]]} changed.`); 261 } 262 } 263 }); 264 ``` 265 2666. Modify properties of the distributed data object. The object properties support basic data types (number, Boolean, and string) and complex data types (array and nested basic types). 267 268 ```ts 269 localObject["name"] = 'jack1'; 270 localObject["age"] = 19; 271 localObject["isVis"] = false; 272 let parentSource1: ParentObject = new ParentObject('jack1 mom', 'jack1 Dad'); 273 localObject["parent"] = parentSource1; 274 ``` 275 276 > **NOTE** 277 > 278 > For the distributed data object of the complex type, only the root property can be modified. The subordinate properties cannot be modified. 279 280 281 ```ts 282 // Supported modification. 283 let parentSource1: ParentObject = new ParentObject('mom', 'Dad'); 284 localObject["parent"] = parentSource1; 285 // Modification not supported. 286 localObject["parent"]["mother"] = 'mom'; 287 ``` 288 2897. Access a distributed data object. Obtain the distributed data object properties, which are the latest data on the network. 290 291 ```ts 292 console.info(`name:${localObject['name']}`); 293 ``` 294 2958. Unsubscribe from data changes. You can specify the callback to unregister. If you do not specify the callback, this API unregisters all data change callbacks of the distributed data object. 296 297 ```ts 298 // Unregister the callback for data changes. 299 localObject.off('change', (sessionId: string, fields: Array<string>) => { 300 console.info("change" + sessionId); 301 if (fields != null && fields != undefined) { 302 for (let index: number = 0; index < fields.length; index++) { 303 console.info("changed !" + fields[index] + " " + localObject[fields[index]]); 304 } 305 } 306 }); 307 // Unregister all data change callbacks. 308 localObject.off('change'); 309 ``` 310 3119. Subscribe to status changes of a distributed data object. A callback will be invoked to report the status change when the target distributed data object goes online or offline. 312 313 ```ts 314 localObject.on('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => { 315 console.info("status changed " + sessionId + " " + status + " " + networkId); 316 // Service processing. 317 }); 318 ``` 319 32010. Save a distributed data object and revoke the data saved. 321 322 ```ts 323 // Save the data object if the device on the network needs to retrieve the object data after the application exits. 324 localObject.save("local").then((result: distributedDataObject.SaveSuccessResponse) => { 325 console.info(`Succeeded in saving. SessionId:${result.sessionId},version:${result.version},deviceId:${result.deviceId}`); 326 }).catch((err: BusinessError) => { 327 console.error(`Failed to save. Code:${err.code},message:${err.message}`); 328 }); 329 330 // Revoke the data saved. 331 localObject.revokeSave().then((result: distributedDataObject.RevokeSaveSuccessResponse) => { 332 console.info(`Succeeded in revokeSaving. Session:${result.sessionId}`); 333 }).catch((err: BusinessError) => { 334 console.error(`Failed to revokeSave. Code:${err.code},message:${err.message}`); 335 }); 336 ``` 337 33811. Unsubscribe from the status changes of a distributed data object. You can specify the callback to unregister. If you do not specify the callback, this API unregisters all status change callbacks of this distributed data object. 339 340 ```ts 341 // Unregister the callback of status changes. 342 localObject.off('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => { 343 console.info("status changed " + sessionId + " " + status + " " + networkId); 344 // Service processing. 345 }); 346 // Unregister all status change callbacks. 347 localObject.off('status'); 348 ``` 349 35012. Remove a distributed data object from the synchronization network. The data of the removed distributed data object will not be synchronized to other devices. 351 352 ```ts 353 localObject.setSessionId(() => { 354 console.info('leave all session.'); 355 }); 356 ``` 357 358### Asset Synchronization Across Devices 359 360The asset type allows the file described by **asset** to be synchronized across devices with its distributed data object. The device that holds the asset file is the source device, and the device that obtains the asset file is the destination device. 361 3621. Import the **@ohos.data.distributedDataObject** and **@ohos.data.commonType** modules. 363 364 ```ts 365 import distributedDataObject from '@ohos.data.distributedDataObject'; 366 import commonType from '@ohos.data.commonType'; 367 ``` 368 3692. Request permissions. 370 371 1. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 372 2. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 373 3743. Create a distributed data object that contains the asset for the source device and add the device to the network. 375 376 ```ts 377 import UIAbility from '@ohos.app.ability.UIAbility'; 378 import type window from '@ohos.window'; 379 import distributedDataObject from '@ohos.data.distributedDataObject'; 380 import commonType from '@ohos.data.commonType'; 381 import type { BusinessError } from '@ohos.base'; 382 383 class Note { 384 title: string | undefined 385 text: string | undefined 386 attachment: commonType.Asset | undefined 387 388 constructor(title: string | undefined, text: string | undefined, attachment: commonType.Asset | undefined) { 389 this.title = title; 390 this.text = text; 391 this.attachment = attachment; 392 } 393 } 394 395 class EntryAbility extends UIAbility { 396 onWindowStageCreate(windowStage: window.WindowStage) { 397 let attachment: commonType.Asset = { 398 name: 'test_img.jpg', 399 uri: 'file://com.example.myapplication/data/storage/el2/distributedfiles/dir/test_img.jpg', 400 path: '/dir/test_img.jpg', 401 createTime: '2024-01-02 10:00:00', 402 modifyTime: '2024-01-02 10:00:00', 403 size: '5', 404 status: commonType.AssetStatus.ASSET_NORMAL 405 } 406 // Create a custom note type that contains an image asset. 407 let note: Note = new Note('test', "test", attachment); 408 let localObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, note); 409 localObject.setSessionId('123456'); 410 } 411 } 412 ``` 413 4144. Create a distributed data object for the destination device and add the device to the network. 415 416 ```ts 417 let note: Note = new Note(undefined, undefined, undefined); 418 let receiverObject: distributedDataObject.DataObject = distributedDataObject.create(this.context, note); 419 receiverObject.on('change', (sessionId: string, fields: Array<string>) => { 420 if (fields.includes('attachment')) { 421 // When the destination device detects the change in the data of the asset type, the synchronization of the asset file is complete. 422 console.info('attachment synchronization completed'); 423 } 424 }); 425 receiverObject.setSessionId('123456'); 426 ``` 427 4285. If the asset is a joint asset, bind the asset and its RDB store to resolve the conflict. 429 430 ```ts 431 const bindInfo: distributedDataObject.BindInfo = { 432 storeName: 'notepad', 433 tableName: 'note_t', 434 primaryKey: { 435 'uuid': '00000000-0000-0000-0000-000000000000' 436 }, 437 field: 'attachment', 438 assetName: attachment.name 439 } 440 441 localObject.bindAssetStore('attachment', bindInfo, (err: BusinessError) => { 442 if (err) { 443 console.error('bindAssetStore failed.'); 444 } 445 console.info('bindAssetStore success.'); 446 }); 447 ```