We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All

Cross-Device Sync of Distributed Data Objects (ArkTS)

When to Use

The 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.

The 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.

The 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. HarmonyOS 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.

Currently, Distributed data objects can be used only in cross-device migration and multi-device collaboration using the cross-device call.

Basic Concepts

  • Distributed in-memory database: The distributed in-memory database caches data in the memory so that applications can quickly access the data. However, the data is not persisted. If the database is closed, the data is not retained.

  • Distributed data object: A distributed data object is encapsulation of a 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.

    The distributed data object has the following states in its lifecycle:

    • Uninitialized: The distributed data object is not instantiated or is destroyed.

    • Local: A data table is created, but the data cannot be synced.

    • 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.

Mechanism

Figure 1 Distributed data object mechanism

The 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.

Encapsulation and Storage of JS Objects

  • An in-memory database is created for each distributed object instance, identified by sessionId. The in-memory databases created by each application are isolated from one another.

  • 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.

  • 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.

Table 1 Correspondence between a distributed data object and a distributed database

Expand
Distributed Object Instance Object Instance Property Name Property Value
Distributed in-memory database A database identified by sessionId Key of a database record Value of a database record

Cross-Device Sync and Data Change Notification

The most important function of a distributed data object is data synchronization between objects. Devices within a trusted network can create a distributed data object locally and set a sessionId. Distributed data objects on different devices establish a synchronization relationship by using the same sessionId.

As shown in the following figure, distributed data object 1 on device A and device B both use session1 as sessionId, establishing a synchronization relationship for session1.

Figure 2 Object sync relationship

For 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.

After 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:

(1) Reading or modifying the data in the session.

(2) Listening for data changes made by other devices.

(3) Listening for status changes, such as the addition and removal of other devices.

When 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).

Minimum Sync Unit

Property 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.

The 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.

Collapse
Word wrap
Dark theme
Copy code
  1. dataObject['parents'] = {mom: "amy"}; // Supported modification
  2. dataObject['parents']['mom'] = "amy"; // Unsupported modification

Figure 3 Sync of distributed data objects

Persistence of Distributed Data Objects

A distributed data object primarily runs in the process space of an application. When the persistence API of the distributed data object is called, the object is persisted and synchronized through a distributed database, ensuring that data is not lost even after the process exits. The distributed database automatically implements synchronization, and you can call on('change') to listen for data changes.

You need to persist distributed data objects in the following scenarios:

  • After a persisted object is created on a device and the application exits, when the application is reopened and a persisted object is created and joins the same session, the data can be restored to the state before the application exited.

  • After a persisted object is created on device A, synchronized, and persisted to device B, if the application on device A exits, when the application on device B is opened and a persisted object is created and joins the same session, the data can be restored to the state before device A's application exited.

Asset Sync Mechanism

In a distributed data object, asset is used to describe a local entity asset file. When the distributed data object is synced across devices, the file is also synced to other devices with it.

In versions earlier than API version 20, only asset is supported, and assets are not supported. To synchronize multiple assets, use each asset as a root property of the distributed data object.

Since API version 20, synchronization of assets is supported.

Constraints

Available APIs

The following are relevant APIs for the cross-device data synchronization feature of distributed objects. For more APIs and usage, see @ohos.data.distributedDataObject (Distributed Data Object).

Expand
Interface Name Description
create(context: Context, source: object): DataObject Creates a distributed data object instance.
genSessionId(): string Generates a session ID for distributed data objects.
setSessionId(sessionId: string, callback: AsyncCallback<void>): void Sets a session ID for data sync. Automatic sync is performed for devices with the same session ID on a trusted network.
setSessionId(callback: AsyncCallback<void>): void Exits all joined sessions.
on(type: 'change', callback: (sessionId: string, fields: Array<string>) => void): void Listens for data changes of a distributed data object.
off(type: 'change', callback?: (sessionId: string, fields: Array<string>) => void): void Cancel listening for data changes of a distributed data object.
on(type: 'status', callback: (sessionId: string, networkId: string, status: 'online' | 'offline' ) => void): void Listens for the online/offline states of a distributed data object.
off(type: 'status', callback?: (sessionId: string, networkId: string, status: 'online' |'offline' ) => void): void Cancels listening for the online/offline states of a distributed data object.
save(deviceId: string, callback: AsyncCallback<SaveSuccessResponse>): void Saves a distributed data object.
revokeSave(callback: AsyncCallback<RevokeSaveSuccessResponse>): void Revokes the saved distributed data object.
bindAssetStore(assetKey: string, bindInfo: BindInfo, callback: AsyncCallback<void>): void Binds a fused asset.
setAsset(assetKey: string, uri: string): void Sets a single asset.
setAssets(assetKey: string, uris: Array<string>): void Sets an asset array.
on(type: 'change', callback: DataObserver<void>): void Listens for data changes of a distributed object.
off(type: 'change', callback?: DataObserver<void>): void Removes a callback instance for listening for distributed object data changes.
on(type: 'status', callback: StatusObserver<void>): void Listens for state changes of a distributed object.
off(type: 'status', callback?: StatusObserver<void>): void Removes a callback instance for listening for distributed object state changes.

How to Develop

Using Distributed Data Objects in Cross-Device Migration

  1. Create a distributed data object in onContinue() for the application on the source device, and save data.

    1.1 Call create() to create a distributed data object instance.

    1.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.

    1.3 Obtain the networkId from wantParam for the application on the target device and call save() with this networkId to save data to the target device.

  2. Create a distributed data object in onCreate() and onNewWant() for the application on the target device, and register a listener for the "restored" state.

    2.1 Call create() to create a distributed data object instance for the application on the target device.

    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.

    2.3 Obtain the sessionId of the source device from want.parameters and call setSessionId to set the same sessionId for the target device.

NOTE
  • During cross-device migration, after setSessionId() is called on the source device to set sessionId, you should call save() to save data to the target device. However, data can only be synced to the target device upon the first call of the save() API. This is because the migration task is completed immediately after data is obtained from the source device for the first time; subsequent data shall be subject to the target device and no further sync is required.

  • When an application is launched as a result of a migration, the onWindowStageRestore() lifecycle callback function, rather than onWindowStageCreate(), is triggered following onCreate() or onNewWant(). This sequence occurs for both cold and hot starts. If you have performed some necessary initialization operations during application launch in onWindowStageCreate(), you must perform the same initialization operations in onWindowStageRestore() after the migration to avoid application exceptions.

  • The continuable tag must be set for cross-device migration. For details, see Application Continuation Development Procedure.

  • The sessionId field in wantParam is used by other services. You are advised to customize a key for accessing the sessionId field.

  • 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.

  • 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.

  • Before API version 20, the asset array is not supported. If multiple files need to be migrated, define an asset data record for each file to migrate. Since API version 20, the asset array can be synced.

  • 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 and File URI.

Collapse
Word wrap
Dark theme
Copy code
  1. import { hilog } from '@kit.PerformanceAnalysisKit';
  2. import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
  3. import { commonType, distributedDataObject } from '@kit.ArkData';
  4. import { BusinessError } from '@kit.BasicServicesKit';
  5. // Service data definition
  6. export class ContentInfo {
  7. public mainTitle: string | undefined;
  8. public textContent: string | undefined;
  9. public imageUriArray: Array<ImageInfo> | undefined;
  10. public isShowLocalInfo: boolean | undefined;
  11. public isAddLocalInfo: boolean | undefined;
  12. public selectLocalInfo: string | undefined;
  13. public attachments?: commonType.Assets | undefined;
  14. constructor(
  15. mainTitle: string | undefined,
  16. textContent: string | undefined,
  17. imageUriArray: Array<ImageInfo> | undefined,
  18. isShowLocalInfo: boolean | undefined,
  19. isAddLocalInfo: boolean | undefined,
  20. selectLocalInfo: string | undefined,
  21. attachments?: commonType.Assets | undefined
  22. ) {
  23. this.mainTitle = mainTitle;
  24. this.textContent = textContent;
  25. this.imageUriArray = imageUriArray;
  26. this.isShowLocalInfo = isShowLocalInfo;
  27. this.isAddLocalInfo = isAddLocalInfo;
  28. this.selectLocalInfo = selectLocalInfo;
  29. this.attachments = attachments;
  30. }
  31. flatAssets(): object {
  32. let obj: object = this;
  33. if (!this.attachments) {
  34. return obj;
  35. }
  36. for (let i = 0; i < this.attachments.length; i++) {
  37. obj[`attachments${i}`] = this.attachments[i];
  38. }
  39. return obj;
  40. }
  41. }
  42. export interface ImageInfo {
  43. /**
  44. * image PixelMap.
  45. */
  46. imagePixelMap: PixelMap;
  47. /**
  48. * Image name.
  49. */
  50. imageName: string;
  51. }
  52. const DOMAIN: number = 0x0000;
  53. const TAG: string = '[DistributedDataObject]';
  54. let dataObject: distributedDataObject.DataObject;
  55. export default class EntryAbility extends UIAbility {
  56. private imageUriArray: Array<ImageInfo> = [];
  57. private distributedObject: distributedDataObject.DataObject | undefined = undefined;
  58. // 1. The migration initiator creates a distributed data object in the onContinue API and saves data to the receiver.
  59. async onContinue(wantParam: Record<string, Object | undefined>): Promise<AbilityConstant.OnContinueResult> {
  60. // 1.1 Obtain the asset key URI of the distributed object to be set.
  61. try {
  62. let sessionId: string = distributedDataObject.genSessionId();
  63. wantParam.distributedSessionId = sessionId;
  64. let distrUriArray: string[] = [];
  65. let assetUriArray = AppStorage.get<Array<string>>('assetUriArray');
  66. if (assetUriArray) {
  67. distrUriArray = assetUriArray;
  68. }
  69. // 1.2 Create a distributed data object.
  70. let contentInfo: ContentInfo = new ContentInfo(
  71. AppStorage.get('mainTitle'),
  72. AppStorage.get('textContent'),
  73. AppStorage.get('imageUriArray'),
  74. AppStorage.get('isShowLocalInfo'),
  75. AppStorage.get('isAddLocalInfo'),
  76. AppStorage.get('selectLocalInfo'),
  77. );
  78. let source = contentInfo.flatAssets();
  79. this.distributedObject = distributedDataObject.create(this.context, source);
  80. // 1.3 Populate the asset or asset array of the distributed object to be set.
  81. if (assetUriArray?.length === 1) {
  82. this.distributedObject?.setAsset('attachments', distrUriArray[0]).then(() => {
  83. hilog.info(DOMAIN, TAG, 'OnContinue setAsset');
  84. })
  85. } else {
  86. this.distributedObject?.setAssets('attachments', distrUriArray).then(() => {
  87. hilog.info(DOMAIN, TAG, 'OnContinue setAssets');
  88. })
  89. }
  90. // 1.4 Save the set asset or asset array to the migration initiator.
  91. this.distributedObject?.setSessionId(sessionId);
  92. this.distributedObject?.save(wantParam.targetDevice as string).catch((err: BusinessError) => {
  93. hilog.error(DOMAIN, TAG, 'OnContinue failed to save. code: ', err.code);
  94. hilog.error(DOMAIN, TAG, 'OnContinue failed to save. message: ', err.message);
  95. });
  96. } catch (error) {
  97. hilog.error(DOMAIN, TAG, 'OnContinue failed code: ', error.code);
  98. hilog.error(DOMAIN, TAG, 'OnContinue failed message: ', error.message);
  99. }
  100. hilog.info(DOMAIN, TAG, 'OnContinue success!');
  101. return AbilityConstant.OnContinueResult.AGREE;
  102. }
  103. // 2. The receiver creates a distributed data object in the onCreate and onNewWant APIs and joins the network for data restoration.
  104. onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  105. if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
  106. if (want.parameters && want.parameters.distributedSessionId) {
  107. this.restoreDistributedObject(want);
  108. }
  109. }
  110. }
  111. // 2. The receiver creates a distributed data object in the onCreate and onNewWant APIs and joins the network for data restoration.
  112. onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  113. if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
  114. if (want.parameters && want.parameters.distributedSessionId) {
  115. this.restoreDistributedObject(want);
  116. }
  117. }
  118. }
  119. async restoreDistributedObject(want: Want): Promise<void> {
  120. if (!want.parameters || !want.parameters.distributedSessionId) {
  121. hilog.error(DOMAIN, TAG, 'missing sessionId');
  122. return;
  123. }
  124. // 2.1 Call create to create a distributed object instance.
  125. let mailInfo: ContentInfo = new ContentInfo(undefined, undefined, [], undefined, undefined, undefined, undefined);
  126. dataObject = distributedDataObject.create(this.context, mailInfo);
  127. // 2.2 Register a restoration state listener. When a callback with the 'restored' state is received, the distributed data object on the receiving end has restored the data saved by the initiating end. If asset data exists, the corresponding files have also been migrated.
  128. dataObject.on('status', (sessionId: string, networkId: string, status: string) => {
  129. hilog.info(DOMAIN, TAG, `status change, sessionId: ${sessionId}`);
  130. hilog.info(DOMAIN, TAG, `status change, networkId: ${networkId}`);
  131. if (status === 'restored') { // Receiving the 'restored' state notification indicates that the data saved by the initiator has been restored.
  132. hilog.info(DOMAIN, TAG, `title: ${dataObject['title']}, text: ${dataObject['text']}`);
  133. AppStorage.setOrCreate('mainTitle', dataObject['mainTitle']);
  134. AppStorage.setOrCreate('textContent', dataObject['textContent']);
  135. AppStorage.setOrCreate('imageUriArray', dataObject['imageUriArray']);
  136. AppStorage.setOrCreate('isShowLocalInfo', dataObject['isShowLocalInfo']);
  137. AppStorage.setOrCreate('isAddLocalInfo', dataObject['isAddLocalInfo']);
  138. AppStorage.setOrCreate('selectLocalInfo', dataObject['selectLocalInfo']);
  139. AppStorage.setOrCreate<Array<ImageInfo>>('imageUriArray', this.imageUriArray);
  140. }
  141. });
  142. // 2.3 Obtain the sessionId placed by the initiator from want.parameters, and call setSessionId to set the sessionId for synchronization.
  143. let sessionId = want.parameters.distributedSessionId as string;
  144. hilog.info(DOMAIN, TAG, `get sessionId: ${sessionId}`);
  145. dataObject.setSessionId(sessionId);
  146. }
  147. }

Using Distributed Data Objects in Multi-Device Collaboration

  1. Call startAbilityByCall to start the peer ability.

    1.1 Call genSessionId to create a session ID, and obtain the network ID of the peer device through the distributed device management API.

    1.2 Assemble a want and add the session ID to the want.

    1.3 Call startAbilityByCall to start the peer ability.

  2. Create a distributed data object and join the network after the peer ability is started.

    2.1 Create a distributed object instance.

    2.2 Register a data change listener.

    2.3 Set the synchronization session ID to join the network.

  3. Create and restore the distributed data objects on the target device after it is started.

    3.1 Create a distributed object instance.

    3.2 Register a data change listener.

    3.3 Obtain the session ID added by the source end from the want, and use this session ID to join the network.

NOTE

The sample code is as follows:

Collapse
Word wrap
Dark theme
Copy code
  1. import { AbilityConstant, Caller, UIAbility, Want } from '@kit.AbilityKit';
  2. import { distributedDataObject } from '@kit.ArkData';
  3. import { distributedDeviceManager } from '@kit.DistributedServiceKit';
  4. import { BusinessError } from '@kit.BasicServicesKit';
  5. import { JSON } from '@kit.ArkTS';
  6. import { hilog } from '@kit.PerformanceAnalysisKit';
  7. // Service data definition
  8. class Data {
  9. public title: string | undefined;
  10. public text: string | undefined;
  11. constructor(title: string | undefined, text: string | undefined) {
  12. this.title = title;
  13. this.text = text;
  14. }
  15. }
  16. const DOMAIN: number = 0x0000;
  17. const TAG: string = '[DistributedDataObject]';
  18. let sessionId: string;
  19. let caller: Caller;
  20. let dataObject: distributedDataObject.DataObject;
  21. const changeCallBack: distributedDataObject.DataObserver = (sessionId: string, fields: Array<string>) => {
  22. console.info(`change, sessionId: ${sessionId}, fields: ${JSON.stringify(fields)}`);
  23. }
  24. export default class EntryAbility extends UIAbility {
  25. // 1. The caller calls startAbilityByCall to start the peer ability.
  26. callRemote() {
  27. if (caller) {
  28. hilog.error(DOMAIN, TAG, 'call remote already');
  29. return;
  30. }
  31. // 1.1 Call genSessionId to create a session ID, and obtain the network ID of the peer device through the distributed device management API.
  32. sessionId = distributedDataObject.genSessionId();
  33. hilog.info(DOMAIN, TAG, `gen sessionId: ${sessionId}`);
  34. let deviceId = getRemoteDeviceId();
  35. if (deviceId === '') {
  36. hilog.warn(DOMAIN, TAG, 'no remote device');
  37. return;
  38. }
  39. hilog.info(DOMAIN, TAG, `get remote deviceId: ${deviceId}`);
  40. // 1.2 Assemble the want and add the session ID to the want.
  41. let want: Want = {
  42. bundleName: 'com.example.collaboration',
  43. abilityName: 'EntryAbility',
  44. deviceId: deviceId,
  45. parameters: {
  46. 'ohos.aafwk.param.callAbilityToForeground': true, // Foreground startup, not mandatory
  47. 'distributedSessionId': sessionId
  48. }
  49. }
  50. try {
  51. // 1.3 Call startAbilityByCall to start the peer ability.
  52. this.context.startAbilityByCall(want).then((res) => {
  53. if (!res) {
  54. hilog.error(DOMAIN, TAG, 'startAbilityByCall failed');
  55. }
  56. caller = res;
  57. })
  58. } catch (e) {
  59. let err = e as BusinessError;
  60. hilog.error(DOMAIN, TAG, `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
  61. }
  62. }
  63. // 2. Create a distributed data object after starting the peer ability.
  64. createDataObject() {
  65. if (!caller) {
  66. hilog.error(DOMAIN, TAG, 'call remote first');
  67. return;
  68. }
  69. if (dataObject) {
  70. hilog.error(DOMAIN, TAG, 'create dataObject already');
  71. return;
  72. }
  73. // 2.1 Create a distributed data object instance.
  74. let data = new Data('The title', 'The text');
  75. dataObject = distributedDataObject.create(this.context, data);
  76. // 2.2 Register a data change listener.
  77. dataObject.on('change', changeCallBack);
  78. // 2.3 Set the synchronization session ID to join the network.
  79. dataObject.setSessionId(sessionId);
  80. }
  81. // 3. After being started, the callee creates and restores the distributed data object.
  82. onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  83. if (want.parameters && want.parameters.distributedSessionId) {
  84. // 3.1 Create a distributed data object instance.
  85. let data = new Data(undefined, undefined);
  86. dataObject = distributedDataObject.create(this.context, data);
  87. // 3.2 Register a data change listener.
  88. dataObject.on('change', changeCallBack);
  89. // 3.3 Obtain the session ID set by the source end from the Want and use it to join the network.
  90. let sessionId = want.parameters.distributedSessionId as string;
  91. hilog.info(DOMAIN, TAG, `onCreate get sessionId: ${sessionId}`);
  92. dataObject.setSessionId(sessionId);
  93. }
  94. }
  95. }
  96. // Obtain devices in the trusted network.
  97. function getRemoteDeviceId() {
  98. let deviceId = '';
  99. try {
  100. let deviceManager = distributedDeviceManager.createDeviceManager('com.example.collaboration');
  101. let devices = deviceManager.getAvailableDeviceListSync();
  102. if (devices[0] && devices[0].networkId) {
  103. deviceId = devices[0].networkId;
  104. }
  105. } catch (e) {
  106. let err = e as BusinessError;
  107. hilog.error(DOMAIN, TAG, `get remote deviceId error, error code: ${err.code}, error message: ${err.message}`);
  108. }
  109. return deviceId;
  110. }
Search in Guides
Enter a keyword.