1# Cross-Device Sync of KV Stores
2
3
4## When to Use
5
6KV Stores are suitable for storing service data with simple relationships. It provides higher read and write performance than the SQL database. KV stores are widely used because the simplicity of the KV data model poses fewer database version compatibility issues in distributed scenarios and simplifies conflict handling in data sync.
7
8
9## Basic Concepts
10
11Before implementing cross-device sync of KV stores, understand the following concepts:
12
13
14### Single KV Store
15
16In a single KV store, data is saved in the unit of a single entry. When data is modified locally, the data entry is updated no matter whether it has been synced. Only one copy of data is retained globally for multiple devices. The data of the latest time is kept for the same entry (with the same primary code) of multiple devices. The data in single KV stores is not differentiated by device. If the data modified on multiple devices has the same key, the value will be overwritten. For the data written or modified locally, the data with the latest time is synced to other devices. Single KV stores are used to store information, such as the Contacts and weather application data.
17
18![singleKVStore](figures/singleKVStore.jpg)
19
20
21### Device KV Store
22
23In a device KV store, the local device ID is added before the key of the KV pair stored by an application. In this way, the data of different devices is isolated. Data is managed by device and can be queried by device.
24
25The underlying devices manage the data by device. The device KV stores support distributed data query by device, but do not support modification of the data synced from peer devices. Device KV stores are used to store the data that needs to be accessed by device, such as the Gallery thumbnails.
26
27![deviceKVStore](figures/deviceKVStore.jpg)
28
29
30## Sync Types
31
32**DatamgrService** provides the following sync types:
33
34### Manual Sync
35
36The application calls **sync()** with the devices to be synced and the sync mode specified to trigger the sync. The sync mode can be **PULL_ONLY** (pulling remote data to the local end), **PUSH_ONLY** (pushing local data to the remote end), or **PUSH_PULL** (pushing local data to the remote end and pulling remote data to the local end). You can use the [**sync()** with the **query** parameter](../reference/apis-arkdata/js-apis-distributedKVStore.md#sync-1) to synchronize the data that meets the specified conditions.
37
38### Auto Sync
39
40<!--RP5-->
41In [multi-device collaboration via cross-device calls](../application-models/hop-multi-device-collaboration.md#using-cross-device-call), after an application updates data, the distributed database automatically pushes the local data to the peer ends and pulls the peer data to the local device for data sync. In this case, the application does not need to call **sync()**.<!--RP5End-->
42
43
44## Working Principles
45
46After completing device discovery and authentication, the underlying communication component notifies the application that the device goes online. The **DatamgrService** then establishes an encrypted transmission channel to synchronize data between the two devices.
47
48
49### Cross-Device Data Sync Mechanism
50
51![kvStore](figures/kvStore.jpg)
52
53When **put()** or **delete()** is called successfully, an auto sync is triggered. The distributed data is sent to the peer device through the communication adaptation layer for sync.
54
55If **sync()** is called successfully, a manual sync is triggered to send distributed data to the peer device through the communication adaptation layer.
56
57
58### Data Change Notification Mechanism
59
60When data is added, deleted, or modified, a notification is sent to the subscriber. The notifications can be classified into the following types:
61
62- Local data change notification: subscription of the application data changes on the local device. When the data in the local KV store is added, deleted, or modified in the database, a notification is received.
63
64- Distributed data change notification: subscription of the application data changes of other devices in the network. When the data in the local KV store changes after being synced with data from another device in the same network, a notification is received.
65
66
67## Constraints
68
69- For each record in a device KV store, the key cannot exceed 896 bytes and the value cannot exceed 4 MB.
70
71- For each record in a single KV store, the key cannot exceed 1 KB and the value cannot exceed 4 MB.
72
73- The KV stores do not support custom conflict resolution policies for applications.
74
75- A maximum of 16 KV stores can be opened simultaneously for an application.
76
77- Each KV store supports a maximum of eight callbacks for subscription of data change notifications.
78
79
80## Available APIs
81
82The following table lists the APIs for cross-device data sync of the single KV store. Most of the APIs are executed asynchronously, using a callback or promise to return the result. The following table uses the callback-based APIs as an example. For more information about the APIs, see [Distributed KV Store](../reference/apis-arkdata/js-apis-distributedKVStore.md).
83
84| API| Description|
85| -------- | -------- |
86| createKVManager(config: KVManagerConfig): KVManager | Creates a **KvManager** instance to manage database objects.|
87| getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void | Obtains a KV store of the specified type.|
88| put(key: string, value: Uint8Array \| string \| number \| boolean, callback: AsyncCallback&lt;void&gt;): void | Inserts and updates data.|
89| on(event: 'dataChange', type: SubscribeType, listener: Callback&lt;ChangeNotification&gt;): void | Subscribes to data changes in the KV store.|
90| get(key: string, callback: AsyncCallback&lt;boolean \| string \| number \| Uint8Array&gt;): void | Queries the value of the specified key.|
91| sync(deviceIds: string[], mode: SyncMode, delayMs?: number): void | Triggers a manual sync of the KV store.|
92
93
94## How to Develop
95
96The following uses a single KV store as an example to describe how to implement cross-device data sync. The development process is as follows.
97
98![kvStore_development_process](figures/kvStore_development_process.png)
99
100> **NOTE**
101>
102> The security level of the destination device (to which data is synced) cannot be higher than that of the source device. For details, see [Access Control Mechanism in Cross-Device Sync](access-control-by-device-and-data-level.md#access-control-mechanism-in-cross-device-sync).
103
1041. Import the module.
105
106   ```ts
107   import { distributedKVStore } from '@kit.ArkData';
108   ```
109
1102. Request permissions.
111
112   (1) Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md).
113   (2) Display a dialog box to ask for user authorization when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md).
114
1153. Create a **KvManager** instance based on the specified **KvManagerConfig** object.
116
117   (1) Create a **kvManagerConfig** object based on the application context.
118   (2) Create a **KvManager** instance.
119
120
121   ```ts
122   // Obtain the context of the stage model.
123   import { window } from '@kit.ArkUI';
124   import { UIAbility } from '@kit.AbilityKit';
125   import { BusinessError } from '@kit.BasicServicesKit';
126
127   let kvManager: distributedKVStore.KVManager | undefined = undefined;
128
129   class EntryAbility extends UIAbility {
130     onWindowStageCreate(windowStage:window.WindowStage) {
131       let context = this.context;
132     }
133   }
134
135    // Obtain the context of the FA model.
136   import { featureAbility } from '@kit.AbilityKit';
137   import { BusinessError } from '@kit.BasicServicesKit';
138
139   let context = featureAbility.getContext();
140
141   // Construct a kvManager instance.
142   try {
143     const kvManagerConfig: distributedKVStore.KVManagerConfig = {
144       bundleName: 'com.example.datamanagertest',
145       context: context
146     }
147     kvManager = distributedKVStore.createKVManager(kvManagerConfig);
148     console.info('Succeeded in creating KVManager.');
149     // Create and obtain the KV store.
150   } catch (e) {
151     let error = e as BusinessError;
152     console.error(`Failed to create KVManager. Code:${error.code},message:${error.message}`);
153   }
154
155   if (kvManager !== undefined) {
156     kvManager = kvManager as distributedKVStore.KVManager;
157     // Perform subsequent operations such as creating a KV store.
158     // ...
159   }
160   ```
161
1624. Obtain the KV store of the specified type.
163
164   (1) Declare the ID of the distributed KV store to create, for example, **'storeId'** in the sample code.
165   (2) Disable the auto sync function (**autoSync:false**) to facilitate subsequent verification of the sync function. If sync is required, call the **sync()** interface.
166
167
168   ```ts
169   let kvStore: distributedKVStore.SingleKVStore | undefined = undefined;
170   try {
171     let child1 = new distributedKVStore.FieldNode('id');
172     child1.type = distributedKVStore.ValueType.INTEGER;
173     child1.nullable = false;
174     child1.default = '1';
175     let child2 = new distributedKVStore.FieldNode('name');
176     child2.type = distributedKVStore.ValueType.STRING;
177     child2.nullable = false;
178     child2.default = 'zhangsan';
179
180     let schema = new distributedKVStore.Schema();
181     schema.root.appendChild(child1);
182     schema.root.appendChild(child2);
183     schema.indexes = ['$.id', '$.name'];
184     // The value 0 indicates the compatible mode, and 1 indicates the strict mode.
185     schema.mode = 1;
186     // Set the number of bytes to be skipped during the value check. The value range is [0, 4M-2].
187     schema.skip = 0;
188
189     const options: distributedKVStore.Options = {
190       createIfMissing: true,
191       encrypt: false,
192       backup: false,
193       autoSync: false,
194       // If kvStoreType is left empty, a device KV store is created by default.
195       // Device KV store: kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
196       kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
197       // The schema parameter is optional. You need to set this parameter when the schema function is required, for example, when predicates are used for query.
198       schema: schema,
199       securityLevel: distributedKVStore.SecurityLevel.S3
200     };
201     kvManager.getKVStore<distributedKVStore.SingleKVStore>('storeId', options, (err, store: distributedKVStore.SingleKVStore) => {
202       if (err) {
203         console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);
204         return;
205       }
206       console.info('Succeeded in getting KVStore.');
207       kvStore = store;
208       // Before performing related data operations, obtain a KV store instance.
209     });
210   } catch (e) {
211     let error = e as BusinessError;
212     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
213   }
214   if (kvStore !== undefined) {
215     kvStore = kvStore as distributedKVStore.SingleKVStore;
216       // Perform subsequent data operations, such as adding, deleting, modifying, and querying data, and subscribing to data changes.
217       // ...
218   }
219   ```
220
2215. Subscribe to distributed data changes. To unsubscribe from the data changes, call [off('dataChange')](../reference/apis-arkdata/js-apis-distributedKVStore.md#offdatachange).
222
223   ```ts
224   try {
225     kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
226       console.info(`dataChange callback call data: ${data}`);
227     });
228   } catch (e) {
229     let error = e as BusinessError;
230     console.error(`An unexpected error occurred. code:${error.code},message:${error.message}`);
231   }
232   ```
233
2346. Write data to the single KV store.
235
236   (1) Construct the key and value to be written to the single KV store.
237   (2) Write KV pairs to the single KV store.
238
239
240   ```ts
241   const KEY_TEST_STRING_ELEMENT = 'key_test_string';
242   // If schema is not defined, pass in other values that meet the requirements.
243   const VALUE_TEST_STRING_ELEMENT = '{"id":0, "name":"lisi"}';
244   try {
245     kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
246       if (err !== undefined) {
247         console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
248         return;
249       }
250       console.info('Succeeded in putting data.');
251     });
252   } catch (e) {
253     let error = e as BusinessError;
254     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
255   }
256   ```
257
2587. Query data in the single KV store.
259
260   (1) Construct the key to be queried from the single KV store.
261   (2) Query data from the single KV store.
262
263
264   ```ts
265   try {
266     kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
267       if (err !== undefined) {
268         console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
269         return;
270       }
271       console.info('Succeeded in putting data.');
272       kvStore = kvStore as distributedKVStore.SingleKVStore;
273       kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
274         if (err != undefined) {
275           console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
276           return;
277         }
278         console.info(`Succeeded in getting data. Data:${data}`);
279       });
280     });
281   } catch (e) {
282     let error = e as BusinessError;
283     console.error(`Failed to get data. Code:${error.code},message:${error.message}`);
284   }
285   ```
286
2878. Synchronize data to other devices.
288
289   Select the devices to be synced with data and the sync mode. The user needs to confirm the sync mode when the application is started for the first time.
290
291   > **NOTE**
292   >
293   > In manual sync mode, **deviceIds** can be obtained by [devManager.getAvailableDeviceListSync](../reference/apis-distributedservice-kit/js-apis-distributedDeviceManager.md#getavailabledevicelistsync).
294
295   ```ts
296   import { distributedDeviceManager } from '@kit.DistributedServiceKit';
297
298   let devManager: distributedDeviceManager.DeviceManager;
299   try {
300     // create deviceManager
301     devManager = distributedDeviceManager.createDeviceManager(context.applicationInfo.name);
302     // deviceIds is obtained by devManager.getAvailableDeviceListSync.
303     let deviceIds: string[] = [];
304     if (devManager != null) {
305       let devices = devManager.getAvailableDeviceListSync();
306       for (let i = 0; i < devices.length; i++) {
307         deviceIds[i] = devices[i].networkId as string;
308       }
309     }
310     try {
311       // 1000 indicates the maximum delay, in ms.
312       kvStore.sync(deviceIds, distributedKVStore.SyncMode.PUSH_ONLY, 1000);
313     } catch (e) {
314       let error = e as BusinessError;
315       console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
316     }
317
318   } catch (err) {
319     let error = err as BusinessError;
320     console.error("createDeviceManager errCode:" + error.code + ",errMessage:" + error.message);
321   }
322   ```
323
324