Skip to main content

React Native Cloud Sync

✨ Features

  • ☁️ Four providers behind one API: NSUbiquitousKeyValueStore (Apple's small key-value iCloud store), CloudKit records, iCloud Drive documents and Google Drive appDataFolder. Use them directly or through one facade.
  • 🔄 Auto-sync between iOS and Android in both directions: mirror writes to iCloud and Drive together, resolve reads back whichever copy is newest, so either platform can be where the user started.
  • 🍏 CloudKit reaches Android and web too, via CloudKit Web Services, against the same private database your iOS app uses.
  • 📂 icloudDocuments writes into the user's own iCloud Drive, so the files show up in Files.app.
  • 🚨 Every failure is a typed rejection (ERR_NOT_SIGNED_IN, ERR_QUOTA_EXCEEDED, ERR_RATE_LIMITED with retryAfterMs, ...); null means only "key doesn't exist".
  • 👤 All five CKAccountStatus values surface as-is, plus onAccountChange with identityChanged, which drops the previous account's caches and queued writes.
  • 🔔 onRemoteChange fires on every provider and the facade, including Google Drive via its change cursor.
  • 📦 Small values go to the key-value store, larger ones to a CKRecord field, binary to a CKAsset or a resumable Drive upload. The size check picks the target.
  • 🔁 Retryable failures queue into a durable outbox: backoff honours retry hints, auto-drains on foreground, bounded, never overwrites a newer write.
  • 🧺 multiGet/multiSet/multiRemove/clear batch for real, one request per provider.
  • 🪝 React hooks from /hooks: useCloudItem, useCloudItems, useCloudCollection, useAccountStatus, usePendingWrites. They drop stale responses and never setState after unmount.
  • 🔐 cloudKitEncrypted uses CloudKit's own encryptedValues, so only ciphertext leaves the device; every other provider has a codec seam for your own cipher.
  • 🧪 An in-memory provider with fault injection, plus the native mock, both exported (/testing, /jest-mock), so every failure path is testable in Jest.
  • ⚙️ React Native 0.71 through 0.86+, old and new architecture, with the #ifdef bridge for the legacy one.

💡 Why?

Cloud storage in React Native is fragmented into single-provider wrappers repeating the same defects: two ship a setItem that reports a failed write as a success (one checks the wrong error variable, the other discards the result entirely), and a third flattens five iCloud account states into one boolean.

A catch { return null } makes "not signed in", "offline", "out of storage" and "no such key" indistinguishable. The app can't tell the user anything useful, or decide whether to retry.

This library was built error contract first, providers second.

📚 Upstream documentation

This package is a wrapper. When something behaves unexpectedly, the answer is usually in Apple's or Google's docs.

Apple

NSUbiquitousKeyValueStoreThe iCloud key-value store; 1 MB / 1024-key limits
CloudKit · CKDatabase · CKRecordRecords in the user's private database
CKAsset · CKRecordZoneBinary assets and custom zones
CKAccountStatusThe five account states this package surfaces verbatim
CloudKit Web ServicesThe REST API behind the Android and web paths
Authentication · Data size limits · Error codesWorth reading before shipping CloudKit on Android
iCloud entitlementsThe keys the config plugin writes
CloudKit ConsoleWhere containers, schemas, API tokens and the sign-in callback live

Google

The appDataFolderThe hidden per-app folder this package stores into
Drive files resourceThe REST endpoints behind the provider
Drive API scopesWhy the scope is drive.appdata