1# Cross-Device Sync of Distributed Data Objects
2
3
4## When to Use
5
6The traditional implementation of data sync 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 sync 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
12Currently, <!--RP2-->distributed data objects can be used only in [cross-device migration](../application-models/hop-cross-device-migration.md) and [multi-device collaboration using the cross-device call](../application-models/hop-multi-device-collaboration.md#using-cross-device-call).<!--RP2End-->
13
14## Basic Concepts
15
16- Distributed in-memory database<br>
17  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.
18
19- Distributed data object<br>
20  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.
21
22  The distributed data object has the following states in its lifecycle:
23
24  - **Uninitialized**: The distributed data object is not instantiated or is destroyed.
25  - **Local**: A data table is created, but the data cannot be synced.
26  - **Distributed**: A data table is created, and data can be synced (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.
27
28
29## Working Principles
30
31**Figure 1** Working mechanism
32
33![distributedObject](figures/distributedObject.jpg)
34
35The 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 sync across devices.
36
37
38### Encapsulation and Storage of JS Objects
39
40- 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.
41
42- 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.
43
44- 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.
45
46**Table 1** Correspondence between a distributed data object and a distributed database
47
48| Distributed Data Object Instance| Object Instance| Property Name| Property Value|
49| -------- | -------- | -------- | -------- |
50| Distributed in-memory database| Database identified by **sessionID**| Key of a record in the database| Value of a record in the database|
51
52
53### Cross-Device Sync and Data Change Notification
54
55One of the most important functions of distributed data objects is to implement data sync 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 synced between them.
56
57As 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 sync relationship of session 1 is established between the two objects.
58
59  **Figure 2** Object sync relationship
60
61![distributedObject_sync](figures/distributedObject_sync.jpg)
62
63For each device, only one distributed data object can be added to a sync 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.
64
65After the sync relationship is established, each session has a copy of shared object data. The distributed data objects added to a session support the following operations:
66
67- Reading or modifying the data in the session.
68
69- Listening for data changes made by other devices.
70
71- Listening for status changes, such as the addition and removal of other devices.
72
73When a distributed data object is added to a session, if its data is different from that of the session, the distributed data object updates data of the session. If you do not want to update the data of the session when adding a distributed data object to a session and obtain the data of the session, set the attribute value of the object to **undefined** (for an asset, set each attribute of the asset to an empty string).
74
75### Minimum Sync Unit
76
77Property 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 synced.
78
79The object properties support basic types (number, Boolean, and string) and complex types (array and nested basic types). For the distributed data object of the complex type, only the root property can be modified. The subordinate properties cannot be modified.
80
81```ts
82dataObject['parents'] = {mom: "amy"}; // Supported modification
83dataObject['parents']['mon'] = "amy"; // Unsupported modification
84```
85
86**Figure 3** Sync of distributed data objects
87
88
89![distributedObject_syncView](figures/distributedObject_syncView.jpg)
90
91
92### Persistence of Distributed Data Objects
93
94Distributed 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.
95
96You need to persist distributed data objects in the following scenarios:
97
98- 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.
99
100- 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.
101
102### Asset Sync Mechanism
103
104In 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 synced across devices, the file is also synced 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.
105
106## Constraints
107
108- Currently, <!--RP2-->distributed data objects can be used only in [cross-device migration](../application-models/hop-cross-device-migration.md) and [multi-device collaboration using the cross-device call](../application-models/hop-multi-device-collaboration.md#using-cross-device-call).<!--RP2End-->
109
110- Only the data of the same application can be synced across devices, that is, the devices must have the same **bundleName**.
111
112- Data can be synced for the distributed data objects with the same session ID.
113
114- Each distributed data object occupies 100 KB to 150 KB of memory. Therefore, you are advised not to create too many distributed data objects.
115
116- The maximum size of a distributed data object is 500 KB.
117
118- 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.
119
120- A maximum of 16 distributed data object instances can be created for an application.
121
122- For the sake of performance and user experience, the maximum number of devices for data collaboration is 3.
123
124- For the distributed data object of the complex type, only the root property can be modified. The subordinate properties cannot be modified. In [asset sync mechanism](#asset-sync-mechanism), the data of the asset type must support modification of its lower-level properties.
125
126- Currently, only JS APIs are supported.
127
128## Available APIs
129
130Most of the APIs for cross-device sync 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).
131
132
133
134| API| Description|
135| -------- | -------- |
136| create(context: Context, source: object): DataObject | Creates a distributed data object instance.|
137| genSessionId(): string | Generates a session ID for distributed data objects.|
138| setSessionId(sessionId: string, callback: AsyncCallback&lt;void&gt;): void | Sets a session ID for data sync. Automatic sync is performed for devices with the same session ID on a trusted network.|
139| setSessionId(callback: AsyncCallback&lt;void&gt;): void | Exits all sessions.|
140| on(type: 'change', callback: (sessionId: string, fields: Array&lt;string&gt;) => void): void | Subscribes to data changes of the distributed data object.|
141| off(type: 'change', callback?: (sessionId: string, fields: Array&lt;string&gt;) => void): void | Unsubscribes from data changes of the distributed data object.|
142| on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' \| 'offline' ) => void): void | Subscribes to status changes of the distributed data object.|
143| off(type: 'status', callback?: (sessionId: string, networkId: string, status: 'online' \|'offline' ) => void): void | Unsubscribes from status changes of the distributed data object.|
144| save(deviceId: string, callback: AsyncCallback&lt;SaveSuccessResponse&gt;): void | Saves a distributed data object.|
145| revokeSave(callback: AsyncCallback&lt;RevokeSaveSuccessResponse&gt;): void | Revokes the saving of the distributed data object.|
146| bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback&lt;void&gt;): void | Binds an asset and its RDB store.|
147
148
149## How to Develop
150
151### Using Distributed Data Objects in Cross-Device Migration
152
1531. Create a distributed data object in **onContinue()** for the application on the source device, and save data.
154
155    (1) Call **create()** to create a distributed data object instance.
156
157    (2) Call **genSessionId()** to generate a **sessionId**, call **setSessionId()** to set a **sessionId**, and add the **sessionId** to **wantParam**. The distributed data objects with the same **sessionId** can connect to the same network.
158
159    (3) Obtain the network ID from **wantParam** for the application on the target device and call **save()** with this network ID to save data to the target device.
160
1612. Create a distributed data object in **onCreate()** and **onNewWant()** for the application on the target device, and register a listener for the "restored" state.
162
163    (1) Call **create()** to create a distributed data object instance for the application on the target device.
164
165    (2) Register a listener callback for the data recovery state. If "restored" is returned by the listener callback registered, the distributed data object of the target device has obtained the data transferred from the source device.
166
167    (3) Obtain the **sessionId** of the source device from **want.parameters** and call **setSessionId** to set the same **sessionId** for the target device.
168
169> **NOTE**
170>
171> - In cross-device migration, after **setsessionId()** is called on the source device to set **sessionId**, call **save()** to save data to the target device.
172>
173<!--RP1-->
174> - The **continuable** tag must be set for cross-device migration. For details, see [How to Develop](../application-models/hop-cross-device-migration.md#how-to-develop).<!--RP1End-->
175>
176> - The **sessionId** field in **wantParam** is used by other services. You are advised to customize a key for accessing the **sessionId** field.
177>
178> - Use data of the Asset type to record information about assets (such as documents, images, and videos). When asset data is migrated, the corresponding asset is also migrated to the target device.
179>
180> - The initial value of the service data must be set to **undefined** on the target device so that the data saved on the source device can be restored on the target device. Otherwise, the data on the source device will be overwritten by the data set on the target device. For asset data, you need to set each attribute of the asset data to an empty string instead of setting the entire asset data to **undefined**.
181>
182> - Currently, the asset array is not supported. If multiple files need to be migrated, define an asset data record for each file to migrate.
183>
184> - Currently, only files in distributed file directory can be migrated. Files in other directories can be copied or moved to distributed file directory before migration. For details about how to move or copy files and obtain URIs, see [File Management](../reference/apis-core-file-kit/js-apis-file-fs.md) and [File URI](../reference/apis-core-file-kit/js-apis-file-fileuri.md).
185
186```ts
187import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
188import { hilog } from '@kit.PerformanceAnalysisKit';
189import { window } from '@kit.ArkUI';
190import { commonType, distributedDataObject } from '@kit.ArkData';
191import { fileIo, fileUri } from '@kit.CoreFileKit';
192import { BusinessError } from '@kit.BasicServicesKit';
193
194// Define service data.
195class Data {
196  title: string | undefined;
197  text: string | undefined;
198  attachment: commonType.Asset; // Use data of the commonType.Asset to record files in the distributed file directory. When asset data is migrated, the corresponding files are also migrated to the target device. (If files do not need to be migrated, do not set this field and createAttachment and createEmptyAttachment.)
199  // attachment2: commonType.Asset; // The asset array is not supported currently. If multiple files need to be migrated, define an asset data record for each file to migrate.
200
201  constructor(title: string | undefined, text: string | undefined, attachment: commonType.Asset) {
202    this.title = title;
203    this.text = text;
204    this.attachment = attachment;
205  }
206}
207
208const TAG = '[DistributedDataObject]';
209let dataObject: distributedDataObject.DataObject;
210
211export default class EntryAbility extends UIAbility {
212  // 1. Create a distributed data object in **onContinue()** for the application on the source device, and save data to the target device.
213  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
214    // 1.1 Call create() to create a distributed data object instance.
215    let attachment = this.createAttachment();
216    let data = new Data('The title', 'The text', attachment);
217    dataObject = distributedDataObject.create(this.context, data);
218
219    // 1.2 Call genSessionId() to generate a sessionId, call setSessionId() to set a sessionId, and add the sessionId to wantParam.
220    let sessionId = distributedDataObject.genSessionId();
221    console.log(TAG + `gen sessionId: ${sessionId}`);
222    dataObject.setSessionId(sessionId);
223    wantParam.distributedSessionId = sessionId;
224
225    // 1.3 Obtain networkId from **wantParam** for the application on the target device and call save() with this network ID to save data to the target device.
226    let deviceId = wantParam.targetDevice as string;
227    console.log(TAG + `get deviceId: ${deviceId}`);
228    dataObject.save(deviceId);
229    return AbilityConstant.OnContinueResult.AGREE;
230  }
231
232  // 2. Create a distributed data object in onCreate() for the application on the target device (for cold start), and add it to the network for data migration.
233  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
234    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
235      if (want.parameters && want.parameters.distributedSessionId) {
236        this.restoreDistributedDataObject(want);
237      }
238    }
239  }
240
241  // 2. Create a distributed data object in onNewWant() for the application on the target device (for hot start), and add it to the network for data migration.
242  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
243    if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
244      if (want.parameters && want.parameters.distributedSessionId) {
245        this.restoreDistributedDataObject(want);
246      }
247    }
248  }
249
250  restoreDistributedDataObject(want: Want) {
251    if (!want.parameters || !want.parameters.distributedSessionId) {
252      console.error(TAG + 'missing sessionId');
253      return;
254    }
255
256    // 2.1 Call create() to create a distributed data object instance for the application on the target device.
257    let attachment = this.createEmptyAttachment(); // Set each attribute of the asset data to an empty string so that the asset data saved on the source device can be restored on the target device.
258    let data = new Data(undefined, undefined, attachment);
259    dataObject = distributedDataObject.create(this.context, data);
260
261    // 2.2 Register a listener callback for the data recovery state. If "restored" is returned by the listener callback registered, the distributed data object of the target device has obtained the data transferred from the source device. If asset data is migrated, the file is also transferred to the target device.
262    dataObject.on('status', (sessionId: string, networkId: string, status: string) => {
263      if (status == 'restored') {// "restored" indicates that the data saved on the source device is restored on the target device.
264        console.log(TAG + `title: ${dataObject['title']}, text: ${dataObject['text']}`);
265      }
266    });
267
268    // 2.3 Obtain the sessionId of the source device from want.parameters and call setSessionId to set the same sessionId for the target device.
269    let sessionId = want.parameters.distributedSessionId as string;
270    console.log(TAG + `get sessionId: ${sessionId}`);
271    dataObject.setSessionId(sessionId);
272  }
273
274  // Create a file in the distributed file directory and use data of the asset type to record the file information. (You can also use the data of asset type to record an existing file in the distributed file directory or copy or move a file from another directory to the distributed file directory and then migrate it.)
275  createAttachment() {
276    let attachment = this.createEmptyAttachment();
277    try {
278      let distributedDir: string = this.context.distributedFilesDir; // Distributed file directory.
279      let fileName: string = 'text_attachment.txt'; // File name.
280      let filePath: string = distributedDir + '/' + fileName; // File path.
281      let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
282      fileIo.writeSync(file.fd, 'The text in attachment');
283      fileIo.closeSync(file.fd);
284      let uri: string = fileUri.getUriFromPath(filePath); // Obtain the file URI.
285      let stat = fileIo.statSync(filePath); // Obtain detailed file attribute information.
286
287      // Write asset data.
288      attachment = {
289        name: fileName,
290        uri: uri,
291        path: filePath,
292        createTime: stat.ctime.toString(),
293        modifyTime: stat.mtime.toString(),
294        size: stat.size.toString()
295      }
296    } catch (e) {
297      let err = e as BusinessError;
298      console.error(TAG + `file error, error code: ${err.code}, error message: ${err.message}`);
299    }
300    return attachment;
301  }
302
303  createEmptyAttachment() {
304    let attachment: commonType.Asset = {
305      name: '',
306      uri: '',
307      path: '',
308      createTime: '',
309      modifyTime: '',
310      size: ''
311    }
312    return attachment;
313  }
314}
315```
316
317### Using Distributed Data Objects in Multi-Device Collaboration
318
3191. Call **startAbilityByCall()** to start an ability on another device.
320
321    (1) Call **genSessionId()** to create a **sessionId** and obtain the network ID of the peer device through the distributed device management interface.
322
323    (2) Assemble **want** and put **sessionId** into **want**.
324
325    (3) Call **startAbilityByCall()** to start the peer ability.
326
3272. Create a distributed data object on the caller device and adds it to the network.
328
329   (1) Create a distributed data object instance.
330
331   (2) Register a listener callback for data changes.
332
333   (3) Set a **sessionId** for the distributed data object and add it to the network.
334
3353. Create a distributed data object on the peer device and restore the data saved on the caller device.
336
337   (1) Create a distributed data object instance on the peer device.
338
339   (2) Register a listener callback for data changes.
340
341   (3) Obtain **sessionId** of the caller device from **want** and add the distributed data object instance to the network with the **sessionId**.
342
343> **NOTE**
344>
345> - Currently, <!--RP3-->distributed data objects can be used only in [multi-device collaboration using the cross-device call](../application-models/hop-multi-device-collaboration.md#using-cross-device-call) to sync data.<!--RP3End-->
346>
347> - To implement multi-device collaboration using the cross-device call, <!--RP4-->you need to apply for the ohos.permission.DISTRIBUTED_DATASYNC permission and set **launchType** to **singleton**. For details, see [How to Develop](../application-models/hop-multi-device-collaboration.md#using-cross-device-call).<!--RP4End-->
348>
349> - The **sessionId** field in **wantParam** is used by other services. You are advised to customize a key for accessing the **sessionId** field.
350>
351> - For details about how to obtain the network ID of the peer device, see [Querying Device Information](../distributedservice/devicemanager-guidelines.md#querying-device-information).
352
353 The sample code is as follows:
354
355```ts
356import { AbilityConstant, Caller, common, UIAbility, Want } from '@kit.AbilityKit';
357import { hilog } from '@kit.PerformanceAnalysisKit';
358import { window } from '@kit.ArkUI';
359import { distributedDataObject } from '@kit.ArkData';
360import { distributedDeviceManager } from '@kit.DistributedServiceKit';
361import { BusinessError } from '@kit.BasicServicesKit';
362
363// Define service data.
364class Data {
365  title: string | undefined;
366  text: string | undefined;
367
368  constructor(title: string | undefined, text: string | undefined) {
369    this.title = title;
370    this.text = text;
371  }
372}
373
374const TAG = '[DistributedDataObject]';
375
376let sessionId: string;
377let caller: Caller;
378let dataObject: distributedDataObject.DataObject;
379
380export default class EntryAbility extends UIAbility {
381  // 1. Call startAbilityByCall() to start an ability on another device.
382  callRemote() {
383    if (caller) {
384      console.error(TAG + 'call remote already');
385      return;
386    }
387    let context = getContext(this) as common.UIAbilityContext;
388
389    // 1.1 Call genSessionId() to create a sessionId and call getRemoteDeviceId() to obtain the network ID of the peer device.
390    sessionId = distributedDataObject.genSessionId();
391    console.log(TAG + `gen sessionId: ${sessionId}`);
392    let deviceId = getRemoteDeviceId();
393    if (deviceId == "") {
394      console.warn(TAG + 'no remote device');
395      return;
396    }
397    console.log(TAG + `get remote deviceId: ${deviceId}`);
398
399    // 1.2 Assemble want and put sessionId into want.
400    let want: Want = {
401      bundleName: 'com.example.collaboration',
402      abilityName: 'EntryAbility',
403      deviceId: deviceId,
404      parameters: {
405        'ohos.aafwk.param.callAbilityToForeground': true, // Start the ability in the foreground. This parameter is optional.
406        'distributedSessionId': sessionId
407      }
408    }
409    try {
410      // 1.3 Call startAbilityByCall() to start the peer ability.
411      context.startAbilityByCall(want).then((res) => {
412        if (!res) {
413          console.error(TAG + 'startAbilityByCall failed');
414        }
415        caller = res;
416      })
417    } catch (e) {
418      let err = e as BusinessError;
419      console.error(TAG + `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
420    }
421  }
422
423  // 2. Create a distributed data object after starting the peer ability.
424  createDataObject() {
425    if (!caller) {
426      console.error(TAG + 'call remote first');
427      return;
428    }
429    if (dataObject) {
430      console.error(TAG + 'create dataObject already');
431      return;
432    }
433    let context = getContext(this) as common.UIAbilityContext;
434
435    // 2.1 Create a distributed data object instance.
436    let data = new Data('The title', 'The text');
437    dataObject = distributedDataObject.create(context, data);
438
439    // 2.2 Register a listener callback for data changes.
440    dataObject.on('change', (sessionId: string, fields: Array<string>) => {
441      fields.forEach((field) => {
442        console.log(TAG + `${field}: ${dataObject[field]}`);
443      });
444    });
445    // 2.3 Set a sessionId for the distributed data object and add it to the network.
446    dataObject.setSessionId(sessionId);
447  }
448
449  // 3. Create a distributed data object on the peer device and restore the data saved on the caller device.
450  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
451    if (want.parameters && want.parameters.distributedSessionId) {
452      // 3.1 Create a distributed data object instance on the peer device.
453      let data = new Data(undefined, undefined);
454      dataObject = distributedDataObject.create(this.context, data);
455
456      // 3.2 Register a listener callback for data changes.
457      dataObject.on('change', (sessionId: string, fields: Array<string>) => {
458        fields.forEach((field) => {
459          console.log(TAG + `${field}: ${dataObject[field]}`);
460        });
461      });
462      // 3.3 Obtain sessionId of the caller device from **want** and add the distributed data object instance to the network with the sessionId.
463      let sessionId = want.parameters.distributedSessionId as string;
464      console.log(TAG + `onCreate get sessionId: ${sessionId}`);
465      dataObject.setSessionId(sessionId);
466    }
467  }
468}
469
470// Obtain devices on the trusted network.
471function getRemoteDeviceId() {
472  let deviceId = "";
473  try {
474    let deviceManager = distributedDeviceManager.createDeviceManager('com.example.collaboration');
475    let devices = deviceManager.getAvailableDeviceListSync();
476    if (devices[0] && devices[0].networkId) {
477      deviceId = devices[0].networkId;
478    }
479  } catch (e) {
480    let err = e as BusinessError;
481    console.error(TAG + `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
482  }
483  return deviceId;
484}
485```