• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![distributedObject](figures/distributedObject.jpg)
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![distributedObject_sync](figures/distributedObject_sync.jpg)
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![distributedObject_syncView](figures/distributedObject_syncView.jpg)
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&lt;void&gt;): 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&lt;void&gt;): void | Exits all sessions.|
133| on(type: 'change', callback: (sessionId: string, fields: Array&lt;string&gt;) => void): void | Subscribes to data changes of the distributed data object.|
134| off(type: 'change', callback?: (sessionId: string, fields: Array&lt;string&gt;) => 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&lt;SaveSuccessResponse&gt;): void | Saves a distributed data object.|
138| revokeSave(callback: AsyncCallback&lt;RevokeSaveSuccessResponse&gt;): void | Revokes the saving of the distributed data object.|
139| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback&lt;void&gt;): 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    ```