DEV Community

HarmonyOS
HarmonyOS

Posted on

Design Architecture for Seamless Data Synchronization Between Phone and Wearable via Wear Engine

Read the original article:Seamless Data Synchronization Between Phone and Wearable via Wear Engine

Requirement Description

A unified architecture is required to synchronize data between a mobile device and a HarmonyOS wearable using the Wear Engine framework. The solution must support real-time updates, offline tolerance, automatic retry on failures, and bi-directional communication with minimal battery and bandwidth consumption.

Background Knowledge

  • HarmonyOS Wear Engine supports phone–wearable communication with capabilities such as messaging, data transfer, and event subscription.
  • Wearable devices have limitations: reduced compute, limited battery, BLE-based transmission characteristics, and strict payload size limits.
  • Data synchronization needs to avoid redundant transfers, support retries with controlled backoff, and ensure consistency and idempotency across both devices.

Implementation Steps

  1. Define Sync Model
    • Identify data types (settings, activity metrics, preferences, user actions).
    • Define lightweight DTOs for Wear Engine transfer.
    • Add a syncId and version/timestamp to each payload for idempotent retries.
  2. Establish Communication Channel
    • Use WearEngineClient for connection initialization on mobile.
    • Use WearEngine.getInstance() on wearable to listen to commands.
    • Define a common message contract: { syncId, type, payload, version }.
  3. Design Sync Flow
    • Trigger-based sync (user action, scheduled interval, API push).
    • State-based sync (hash comparison to avoid unnecessary payload).
    • Conflict resolution rule (mobile-first, wearable-first, timestamp-based).
  4. Retry Mechanism Design (Core Part)
    • Maintain a Retry Queue on the sender side:
      • Each item: { syncId, payload, attemptCount, nextRetryAt }.
    • On send failure or missing ACK within timeout:
      • Increment attemptCount.
      • Calculate nextRetryAt using exponential backoff (e.g. baseDelay * 2^(attemptCount-1) with an upper limit).
      • Re-enqueue the item if attemptCount < MAX_RETRY.
    • On ACK received:
      • Remove the item from the Retry Queue.
    • Ensure all operations are idempotent:
      • Receiver uses syncId + version to detect duplicates.
      • If syncId already processed with same or higher version, ignore but still send ACK.
  5. Architectural Layers
    • Sync Manager (Mobile):
      • Builds sync payloads, pushes to Transport Layer, manages Retry Queue and timeout/ACK logic.
    • Sync Service (Wearable):
      • Receives updates, validates syncId/version, applies changes, and sends ACK.
    • Local Cache Layer:
      • Stores last applied syncId + version for quick duplicate detection.
    • Transport Layer:
      • Wear Engine messaging (send/receive).
  6. Data Packaging Strategy
    • Serialize data to JSON or binary.
    • Compress if needed and ensure payload < BLE recommended limits.
    • Include meta fields: syncId, version, type.
  7. Sequence Diagram Explanation
    • Mobile triggers sync
    • Sync Manager sends message via Wear Engine
    • Wearable receives, checks duplicate, applies update
    • Wearable sends ACK containing syncId + result
    • Mobile marks sync item as completed and removes it from Retry Queue.
    • If ACK not received in T seconds → retry logic kicks in.

Code Snippet

Sender Side (Mobile) : Basic Retry Skeleton

interface SyncItem {
  syncId: string;
  payload: any;
  attemptCount: number;
  nextRetryAt: number;
}

const MAX_RETRY = 5;
const BASE_DELAY_MS = 2000; // 2 seconds

let retryQueue: SyncItem[] = [];

function sendWithRetry(deviceId: string, payload: any) {
  const syncId = generateSyncId();
  const item: SyncItem = {
    syncId,
    payload: { ...payload, syncId },
    attemptCount: 0,
    nextRetryAt: Date.now()
  };
  retryQueue.push(item);
  processRetryQueue(deviceId);
}

function processRetryQueue(deviceId: string) {
  const now = Date.now();
  retryQueue
    .filter(item => item.nextRetryAt <= now)
    .forEach(item => {
      wearEngineClient.sendMessage(
        deviceId,
        "SYNC_REQUEST",
        JSON.stringify(item.payload),
        (err) => {
          if (err) {
            item.attemptCount++;
            if (item.attemptCount < MAX_RETRY) {
              const delay = BASE_DELAY_MS * Math.pow(2, item.attemptCount - 1);
              item.nextRetryAt = now + Math.min(delay, 30000); // cap at 30s
            } else {
              // mark as failed / log for monitoring
              removeItemFromQueue(item.syncId);
            }
          } else {
            // message send ok – still wait for ACK
            // optional: start ACK timeout timer here
          }
        }
      );
    });
}

// Called when ACK is received from wearable
function onAckReceived(syncId: string) {
  removeItemFromQueue(syncId);
}

function removeItemFromQueue(syncId: string) {
  retryQueue = retryQueue.filter(item => item.syncId !== syncId);
}
Enter fullscreen mode Exit fullscreen mode

Receiver Side (Wearable) : Idempotent Apply + ACK

let processedMap: Map<string, number> = new Map(); // syncId -> version

wearEngine.onMessageReceived((msg) => {
  const data = JSON.parse(msg.data);
  const { syncId, version } = data;
  const lastVersion = processedMap.get(syncId) ?? 0;

  if (version > lastVersion) {
    applySync(data);  // domain-specific logic
    processedMap.set(syncId, version);
  }
  // Always ACK, even on duplicate, so sender stops retrying
  wearEngine.sendMessage(msg.sourceDeviceId, "SYNC_ACK", JSON.stringify({ syncId }));
});
Enter fullscreen mode Exit fullscreen mode

Note: In the actual project, error management, persistent storage (persistent map), and timer management will be detailed according to the project.

Test Results

  • Verified sync reliability with 50+ consecutive transfers under normal conditions.
  • Simulated temporary connection loss: confirmed that retry mechanism successfully completed sync once the connection was restored.
  • Verified that duplicate messages (due to retries) did not create inconsistent state on the wearable because of syncId + version checks.
  • Measured that exponential backoff reduced battery impact compared to constant-interval retries.

Limitations

  • Excessive retries still consume battery; set reasonable MAX_RETRY and cap maximum delay.
  • For long offline periods, it may be better to collapse multiple pending items into a single aggregated sync.
  • BLE transmission may drop under poor signal or OS power restrictions; retry cannot fully guarantee delivery.
  • Idempotency logic (syncId, version) must be properly implemented to avoid inconsistent states.
  • Persistent storage for processedMap may be required if the wearable frequently restarts.

Related Documents or Links

Written by Hasan Kaya

Top comments (0)