[AIT-1038] chore(examples): migrate LiveObjects example app to the path-based API#1225
Conversation
The example app targeted the removed io.ably.lib.objects callback API (getRootAsync, createCounterAsync, LiveMapValue.asLiveCounter). Rewrite its bridge layer (Utils.kt) against the public path-based interfaces in io.ably.lib.liveobjects, entered via channel.object.get() which returns the root LiveMapPathObject once objects are SYNCED. PathObjects reference a location and re-resolve on every call, which simplifies the app considerably: - get-or-create is a single atomic publish: root.set(key, LiveMapValue.of(LiveCounter.create())) replaces the old free-floating create + link two-step - the root-map subscription that watched for "Reset all" replacing a counter (and rebound to the new instance) is gone entirely - a path subscription follows the replacement automatically - mutations call the CompletableFuture API directly, fire-and-forget: the path subscription refreshes Compose state on ack, so the *Coroutine wrapper extensions and their scope.launch call sites are removed UI, test tags, channel setup and the instrumentation tests are unchanged. Verified with :examples:assembleDebug, :examples:compileDebugAndroidTestKotlin and manually on an emulator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 24 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughThe example app's LiveObjects utility layer is migrated from the coroutine-based ChangesExample app path-based LiveObjects migration
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Screen as ColorVotingScreen/TaskManagementScreen
participant Utils as Utils.kt helpers
participant Root as LiveMapPathObject root
participant Path as LiveCounterPathObject/entries
Screen->>Utils: observeRootObject(channel)
Utils->>Root: channel.object.get().await()
Root-->>Utils: LiveMapPathObject
Utils-->>Screen: root
Screen->>Utils: observeCounter(root, key) / observeMap(root, key)
Utils->>Root: getOrCreateCounter/getOrCreateMap
Root-->>Utils: LiveCounterPathObject/entries
Utils->>Path: subscribe to path
Path-->>Utils: update on change
Utils-->>Screen: CounterState / entries map
Screen->>Path: increment(1) / set(...) / remove(...)
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Migrates the LiveObjects Android example app from the removed callback-based io.ably.lib.objects API to the newer path-based io.ably.lib.liveobjects API, consolidating LiveObjects interactions into Utils.kt and simplifying the Compose screens to use path objects directly.
Changes:
- Replaced callback/coroutine adapter layer with direct path-based reads/writes and
CompletableFutureusage (awaited only where ordering matters). - Updated Compose observers to subscribe via
PathObject.subscribe(...)and rely on path re-resolution across object replacement. - Updated task-management and color-voting screens to call path-object mutators directly (fire-and-forget).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| examples/src/main/kotlin/com/ably/example/Utils.kt | Reworks the bridge layer to fetch the root via channel.object.get().await(), create/link counters and maps via path-based API, and updates Compose observers/subscriptions. |
| examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt | Switches map mutations to path-based set/remove calls and removes coroutine-scope wrappers. |
| examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt | Switches counter mutations to path-based increment calls and removes coroutine-scope wrappers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt (1)
47-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winBind the Red card enable state to
redCounter
enabled = greenCounter != nullmakes the Red button depend on the Green handle, so it can be disabled when Red is ready and enabled when Red is not. UseredCounter != nullhere to match the other cards.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt` around lines 47 - 56, The Red vote card in ColorVotingScreen is using the wrong handle for its enabled state, so it currently depends on greenCounter instead of its own redCounter. Update the ColorVoteCard call in the Red card section to use redCounter != null for enabled, matching the pattern used by the other cards and keeping the Red button state tied to its own subscription.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt`:
- Around line 77-85: The Add Task action in TaskManagementScreen can clear user
input even when liveTasks is still null, causing silent task loss before sync
completes. Update the Button/onClick flow so it is disabled until liveTasks is
available, or only reset taskText after a successful liveTasks.set call; use the
liveTasks state and the Add Task button in TaskManagementScreen as the key
places to fix this.
In `@examples/src/main/kotlin/com/ably/example/Utils.kt`:
- Around line 49-68: Both getOrCreateCounter and getOrCreateMap rely on a
non-atomic exists() check before root.set(), which can race with other clients
and overwrite existing data. Update these helpers to use a concurrency-safe
create-if-absent approach around LiveMapPathObject, LiveCounterPathObject, and
LiveMapValue so the counter/map is only created when the key is truly absent.
Keep the existing returned types and linking behavior, but remove the
check-then-set pattern that can replace an existing counter or populated map.
---
Outside diff comments:
In `@examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt`:
- Around line 47-56: The Red vote card in ColorVotingScreen is using the wrong
handle for its enabled state, so it currently depends on greenCounter instead of
its own redCounter. Update the ColorVoteCard call in the Red card section to use
redCounter != null for enabled, matching the pattern used by the other cards and
keeping the Red button state tied to its own subscription.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 253a0982-9655-41c4-9562-ab6494e0ae73
📒 Files selected for processing (3)
examples/src/main/kotlin/com/ably/example/Utils.ktexamples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.ktexamples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt
Review fixes for #1225: - getOrCreateCounter/getOrCreateMap now check the resolved type (getType() != LIVE_COUNTER/LIVE_MAP) instead of exists(), so a wrong-typed value at the key is replaced rather than returned as a view whose reads yield null and writes fail; the LWW resolution of the inherent check-then-set race is documented on both helpers - observeCounter/observeMap effects keyed on (root, key) so a changed key rebinds - Red vote card enable state now tracks redCounter (was greenCounter, a pre-existing copy/paste bug) - Add Task is disabled until the tasks map is bound, so input is never cleared without the task being enqueued - new ObjectsSyncStatusRow on both screens surfaces objects sync progress ("Objects syncing..." -> "Objects synced") via the channel.object.on(SYNCING/SYNCED) state API, with the initial state derived from root readiness since state events fire on transitions only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migrates the LiveObjects example Android app from the removed
io.ably.lib.objectscallback API to the new path-based public API (io.ably.lib.liveobjects) introduced by #1224. The app is the integration testbed for the SDK — it consumes:liveobjectsand:androidas local Gradle modules — so it needs to track the API it exercises. Verified on an emulator: color voting, reset-all, and task management all sync live.What changed
All SDK usage lives in
Utils.kt(the app's bridge layer); the two screens only needed import/call-site touches. UI, test tags, channel setup (object_publish/object_subscribemodes) and both instrumentation tests are unchanged.channel.objects.getRootAsync(callback)→LiveMapchannel.object.get().await()→ rootLiveMapPathObject(completes once objects areSYNCED)createCounterAsync/createMapAsync+root.setAsync(key, ...)(two steps, free-floating object linked afterwards)root.set(key, LiveMapValue.of(LiveCounter.create()))— create + link in one published operationcounter.incrementAsync(n, callback)wrapped inincrementCoroutinecounterPath.increment(n)called directly (returnsCompletableFuture)map.setAsync/removeAsyncwrapped insetCoroutine/removeCoroutinemapPath.set(key, value)/mapPath.remove(key)called directlycounter.subscribe(listener)+ separate root-map subscription watching for the key beingUPDATED, to rebind after "Reset all" replaces the counterPathObjectreferences a location and re-resolves per call, so it follows object replacement automaticallyLiveMapValue.asStringon map entries (throws on non-string)pathObject.asString().value()(null-safe; non-string entries are skipped)Design notes
PathObjectreferences stay valid across object replacement — which is exactly the app's "Reset all" flow. This deleted the entire rebind bookkeeping the old app carried. The identity-boundInstanceAPI isn't needed anywhere in this app;Utils.kt's file-level KDoc documents the distinction for readers using it as reference code.CompletableFuture-returning mutators directly without awaiting; the path subscription refreshes Compose state when the operation is acknowledged. This removed allscope.launchwrappers from the screens. Failures are dropped silently — same observable behaviour as the old app's swallow-all wrapper, and equally demo-only.getOrCreateCounter/getOrCreateMapawait the create-and-link before binding (so vote buttons enable only once the counter exists), and the root fetch awaits sync.Testing
:examples:assembleDebugand:examples:compileDebugAndroidTestKotlingreen.MainScreenTest,ColorVotingScreenTest) are untouched and remain runnable via:examples:connectedDebugAndroidTest.Net: −120 lines (116 added, 236 removed), almost all of it callback-adapter and rebind machinery the path-based API makes unnecessary.
Summary by CodeRabbit