|
| 1 | +--- |
| 2 | +path: "/retail-demo-app-services-sync" |
| 3 | +title: Real-Time Sync with Couchbase Capella App Services |
| 4 | +short_title: App Services Sync |
| 5 | +description: |
| 6 | + - Learn how Couchbase Lite syncs data with Capella App Services |
| 7 | + - Understand the replication architecture and configuration |
| 8 | + - Configure and test bidirectional sync in the retail demo |
| 9 | +content_type: tutorial |
| 10 | +filter: mobile |
| 11 | +technology: |
| 12 | + - mobile |
| 13 | + - capella |
| 14 | + - app-services |
| 15 | +tags: |
| 16 | + - Android |
| 17 | + - iOS |
| 18 | + - App Services |
| 19 | +sdk_language: |
| 20 | + - kotlin |
| 21 | + - swift |
| 22 | +length: 30 Mins |
| 23 | +exclude_tutorials: true |
| 24 | +--- |
| 25 | + |
| 26 | +## Introduction |
| 27 | + |
| 28 | +In this tutorial, you will learn how the Couchbase Lite Retail Demo implements real-time data synchronization with Couchbase Capella App Services. This enables your inventory data to stay synchronized across all devices and the cloud backend. |
| 29 | + |
| 30 | +You will learn: |
| 31 | + |
| 32 | +* How Couchbase Lite replication works |
| 33 | +* The sync flow for login, profile fetch, and continuous replication |
| 34 | +* How to configure sync in the iOS, Android, and Web apps |
| 35 | +* How to test and verify synchronization |
| 36 | + |
| 37 | +## Prerequisites |
| 38 | + |
| 39 | +Before starting this tutorial, ensure you have: |
| 40 | + |
| 41 | +* Completed the [Capella Setup tutorial](/retail-demo-capella-setup) |
| 42 | +* Your App Services Public Connection URL |
| 43 | +* User credentials created in App Services |
| 44 | +* The retail demo app built and running on your platform |
| 45 | + |
| 46 | +## How Sync Works |
| 47 | + |
| 48 | +### Replication Architecture |
| 49 | + |
| 50 | +Couchbase Lite uses a **replicator** to synchronize data between the local database and a remote endpoint (App Services). The replicator supports: |
| 51 | + |
| 52 | +| Mode | Description | |
| 53 | +|------|-------------| |
| 54 | +| **Push** | Send local changes to the server | |
| 55 | +| **Pull** | Receive server changes locally | |
| 56 | +| **Push and Pull** | Bidirectional synchronization | |
| 57 | + |
| 58 | +### Sync Flow in the Retail Demo |
| 59 | + |
| 60 | +When a user logs in, the app follows this sync flow: |
| 61 | + |
| 62 | +``` |
| 63 | +1. User Login |
| 64 | + └── Authenticate with App Services endpoint |
| 65 | +
|
| 66 | +2. Profile Fetch (One-Shot Pull) |
| 67 | + └── Pull store profile document |
| 68 | + └── Update UI with store information |
| 69 | +
|
| 70 | +3. Continuous Replication (Push & Pull) |
| 71 | + └── Sync inventory collection |
| 72 | + └── Sync orders collection |
| 73 | + └── Real-time updates across all devices |
| 74 | +``` |
| 75 | + |
| 76 | +## Understanding the Code |
| 77 | + |
| 78 | +### iOS Implementation |
| 79 | + |
| 80 | +In the iOS app, sync is managed by `AppServicesSyncManager.swift`: |
| 81 | + |
| 82 | +```swift |
| 83 | +// Configure the replicator |
| 84 | +var config = ReplicatorConfiguration(target: URLEndpoint(url: syncURL)) |
| 85 | +config.continuous = true |
| 86 | +config.replicatorType = .pushAndPull |
| 87 | + |
| 88 | +// Add collections to sync |
| 89 | +config.addCollections([inventoryCollection, ordersCollection, profileCollection]) |
| 90 | + |
| 91 | +// Set authentication |
| 92 | +config.authenticator = BasicAuthenticator(username: username, password: password) |
| 93 | + |
| 94 | +// Create and start replicator |
| 95 | +let replicator = Replicator(config: config) |
| 96 | +replicator.start() |
| 97 | +``` |
| 98 | + |
| 99 | +Key configuration options: |
| 100 | + |
| 101 | +| Property | Value | Description | |
| 102 | +|----------|-------|-------------| |
| 103 | +| `continuous` | `true` | Keeps replicating until stopped | |
| 104 | +| `replicatorType` | `.pushAndPull` | Bidirectional sync | |
| 105 | +| `authenticator` | `BasicAuthenticator` | Username/password auth | |
| 106 | + |
| 107 | +### Android Implementation |
| 108 | + |
| 109 | +In the Android app, sync is managed by `AppServicesSyncManager.kt`: |
| 110 | + |
| 111 | +```kotlin |
| 112 | +// Create endpoint |
| 113 | +val endpoint = URLEndpoint(URI(syncUrl)) |
| 114 | + |
| 115 | +// Configure replicator |
| 116 | +val config = ReplicatorConfiguration(endpoint) |
| 117 | + .addCollections(listOf(inventoryCollection, ordersCollection, profileCollection), null) |
| 118 | + .setType(ReplicatorType.PUSH_AND_PULL) |
| 119 | + .setContinuous(true) |
| 120 | + .setAuthenticator(BasicAuthenticator(username, password.toCharArray())) |
| 121 | + |
| 122 | +// Create and start replicator |
| 123 | +replicator = Replicator(config) |
| 124 | +replicator.start() |
| 125 | +``` |
| 126 | + |
| 127 | +### Web Implementation |
| 128 | + |
| 129 | +The web app uses `couchbase-lite-js` for sync: |
| 130 | + |
| 131 | +```typescript |
| 132 | +// Configure replicator |
| 133 | +const config = new ReplicatorConfiguration(database, new URLEndpoint(syncUrl)); |
| 134 | +config.continuous = true; |
| 135 | +config.replicatorType = ReplicatorType.PUSH_AND_PULL; |
| 136 | +config.authenticator = new BasicAuthenticator(username, password); |
| 137 | + |
| 138 | +// Add collections |
| 139 | +config.addCollections([inventoryCollection, ordersCollection, profileCollection]); |
| 140 | + |
| 141 | +// Start replication |
| 142 | +const replicator = new Replicator(config); |
| 143 | +await replicator.start(); |
| 144 | +``` |
| 145 | + |
| 146 | +## Configuring Sync in the Apps |
| 147 | + |
| 148 | +### iOS Configuration |
| 149 | + |
| 150 | +1. Open `LiquorApp/Info.plist` or set environment variables: |
| 151 | + |
| 152 | +```bash |
| 153 | +export CBL_BASE_URL="wss://your-endpoint.apps.cloud.couchbase.com:4984" |
| 154 | +export CBL_AA_DB="supermarket-aa" |
| 155 | +export CBL_NYC_DB="supermarket-nyc" |
| 156 | +export CBL_AA_USER="aa-store-01@supermarket.com" |
| 157 | +export CBL_NYC_USER="nyc-store-01@supermarket.com" |
| 158 | +export CBL_PASSWORD="P@ssword1" |
| 159 | +``` |
| 160 | + |
| 161 | +2. The app reads these values in `AppConfig.swift` and uses them to configure the replicator. |
| 162 | + |
| 163 | +### Android Configuration |
| 164 | + |
| 165 | +1. Set environment variables before launching Android Studio: |
| 166 | + |
| 167 | +```bash |
| 168 | +export CBL_BASE_URL="wss://your-endpoint.apps.cloud.couchbase.com:4984" |
| 169 | +export CBL_AA_DB="supermarket-aa" |
| 170 | +export CBL_NYC_DB="supermarket-nyc" |
| 171 | +export CBL_AA_USER="aa-store-01@supermarket.com" |
| 172 | +export CBL_NYC_USER="nyc-store-01@supermarket.com" |
| 173 | +export CBL_PASSWORD="P@ssword1" |
| 174 | +``` |
| 175 | + |
| 176 | +2. Or add to `gradle.properties`: |
| 177 | + |
| 178 | +```properties |
| 179 | +CBL_BASE_URL=wss://your-endpoint.apps.cloud.couchbase.com:4984 |
| 180 | +CBL_AA_DB=supermarket-aa |
| 181 | +CBL_NYC_DB=supermarket-nyc |
| 182 | +``` |
| 183 | + |
| 184 | +### Web Configuration |
| 185 | + |
| 186 | +1. Copy the environment example and edit: |
| 187 | + |
| 188 | +```bash |
| 189 | +cd web |
| 190 | +cp .env.example .env |
| 191 | +``` |
| 192 | + |
| 193 | +2. Set the sync URL in `.env`: |
| 194 | + |
| 195 | +```env |
| 196 | +VITE_APP_SERVICES_URL=wss://your-endpoint.apps.cloud.couchbase.com:4984/supermarket-nyc |
| 197 | +``` |
| 198 | + |
| 199 | +## Try It Out |
| 200 | + |
| 201 | +### Step 1: Start the App |
| 202 | + |
| 203 | +1. Launch the retail demo app on your platform |
| 204 | +2. The login screen should appear |
| 205 | + |
| 206 | +### Step 2: Log In |
| 207 | + |
| 208 | +Log in with your App Services credentials: |
| 209 | + |
| 210 | +* **Email**: `nyc-store-01@supermarket.com` |
| 211 | +* **Password**: `P@ssword1` |
| 212 | + |
| 213 | +The app will: |
| 214 | +1. Authenticate with App Services |
| 215 | +2. Pull the store profile |
| 216 | +3. Start continuous replication |
| 217 | + |
| 218 | +### Step 3: View Inventory |
| 219 | + |
| 220 | +Navigate to the Inventory screen. You should see items synced from your Capella cluster. |
| 221 | + |
| 222 | +### Step 4: Make a Change |
| 223 | + |
| 224 | +1. Select an inventory item |
| 225 | +2. Update the stock quantity |
| 226 | +3. Save the change |
| 227 | + |
| 228 | +The change is: |
| 229 | +1. Saved locally to Couchbase Lite |
| 230 | +2. Pushed to App Services |
| 231 | +3. Synced to Capella and other connected devices |
| 232 | + |
| 233 | +### Step 5: Verify in Capella |
| 234 | + |
| 235 | +1. Log into [Couchbase Capella](https://cloud.couchbase.com/) |
| 236 | +2. Navigate to your cluster > Tools > Documents |
| 237 | +3. Browse the `inventory` collection in the appropriate scope |
| 238 | +4. Find your updated document and verify the change |
| 239 | + |
| 240 | +## Testing Multi-Device Sync |
| 241 | + |
| 242 | +To see real-time sync in action: |
| 243 | + |
| 244 | +1. **Run two instances** of the app (different emulators, devices, or platforms) |
| 245 | +2. **Log in as the same store** on both (e.g., `nyc-store-01@supermarket.com`) |
| 246 | +3. **Update inventory** on one device |
| 247 | +4. **Observe the change** appear on the other device |
| 248 | + |
| 249 | +This demonstrates the continuous bidirectional sync capability. |
| 250 | + |
| 251 | +## Handling Sync Status |
| 252 | + |
| 253 | +The demo apps display sync status to help you understand what's happening: |
| 254 | + |
| 255 | +| Status | Meaning | |
| 256 | +|--------|---------| |
| 257 | +| **Connecting** | Establishing connection to App Services | |
| 258 | +| **Busy** | Actively syncing data | |
| 259 | +| **Idle** | Connected, waiting for changes | |
| 260 | +| **Offline** | No network connection (changes queued locally) | |
| 261 | +| **Stopped** | Replication manually stopped | |
| 262 | + |
| 263 | +### Monitoring Sync Events |
| 264 | + |
| 265 | +In the code, you can listen to replicator status changes: |
| 266 | + |
| 267 | +```swift |
| 268 | +// iOS |
| 269 | +replicator.addChangeListener { change in |
| 270 | + print("Status: \(change.status.activity)") |
| 271 | + print("Progress: \(change.status.progress.completed)/\(change.status.progress.total)") |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +```kotlin |
| 276 | +// Android |
| 277 | +replicator.addChangeListener { change -> |
| 278 | + Log.d("Sync", "Status: ${change.status.activityLevel}") |
| 279 | + Log.d("Sync", "Progress: ${change.status.progress.completed}/${change.status.progress.total}") |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +## Conflict Resolution |
| 284 | + |
| 285 | +When the same document is modified on multiple devices before syncing, a conflict occurs. Couchbase Lite provides automatic conflict resolution: |
| 286 | + |
| 287 | +* **Default**: Last-write-wins based on revision history |
| 288 | +* **Custom**: You can implement custom conflict resolvers |
| 289 | + |
| 290 | +For the retail demo, the default conflict resolution is used. Learn more in the [conflict handling documentation](https://docs.couchbase.com/couchbase-lite/current/swift/conflict.html). |
| 291 | + |
| 292 | +## Troubleshooting |
| 293 | + |
| 294 | +### Sync Not Starting |
| 295 | + |
| 296 | +* Verify your `CBL_BASE_URL` includes `wss://` protocol |
| 297 | +* Check that credentials match those in App Services |
| 298 | +* Ensure App Services endpoint is running |
| 299 | + |
| 300 | +### Authentication Failures |
| 301 | + |
| 302 | +* Verify username format: `store-id@supermarket.com` |
| 303 | +* Check password matches exactly (case-sensitive) |
| 304 | +* Confirm user exists in the correct App Endpoint |
| 305 | + |
| 306 | +### Data Not Appearing |
| 307 | + |
| 308 | +* Verify collections are linked in your App Endpoint |
| 309 | +* Check that data was imported into the correct scope |
| 310 | +* Look for errors in app console/logs |
| 311 | + |
| 312 | +### Connection Timeouts |
| 313 | + |
| 314 | +* Check network connectivity |
| 315 | +* Verify App Services URL is accessible |
| 316 | +* Try pinging the endpoint from your development machine |
| 317 | + |
| 318 | +## Exercise: Create and Sync an Order |
| 319 | + |
| 320 | +1. Log into the app as `nyc-store-01@supermarket.com` |
| 321 | +2. Navigate to an inventory item |
| 322 | +3. Create a new order for that item |
| 323 | +4. Open Capella and navigate to the `orders` collection |
| 324 | +5. Verify the new order document appears |
| 325 | +6. Change the order status in Capella to "Processing" |
| 326 | +7. Return to the app and verify the status update synced |
| 327 | + |
| 328 | +## Learn More |
| 329 | + |
| 330 | +### References |
| 331 | + |
| 332 | +* [Couchbase Lite Replication](https://docs.couchbase.com/couchbase-lite/current/swift/replication.html) |
| 333 | +* [App Services Documentation](https://docs.couchbase.com/cloud/app-services/index.html) |
| 334 | +* [Conflict Resolution](https://docs.couchbase.com/couchbase-lite/current/swift/conflict.html) |
| 335 | +* [Replicator Configuration](https://docs.couchbase.com/couchbase-lite/current/swift/replication.html#lbl-cfg-repl) |
0 commit comments