Skip to content

[AIT-1038] chore(examples): migrate LiveObjects example app to the path-based API#1225

Open
sacOO7 wants to merge 2 commits into
feature/liveobjects-remaining-kotlin-implementationfrom
chore/update-liveobjects-example-app
Open

[AIT-1038] chore(examples): migrate LiveObjects example app to the path-based API#1225
sacOO7 wants to merge 2 commits into
feature/liveobjects-remaining-kotlin-implementationfrom
chore/update-liveobjects-example-app

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Migrates the LiveObjects example Android app from the removed io.ably.lib.objects callback 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 :liveobjects and :android as 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_subscribe modes) and both instrumentation tests are unchanged.

Old (callback API) New (path-based API)
channel.objects.getRootAsync(callback)LiveMap channel.object.get().await() → root LiveMapPathObject (completes once objects are SYNCED)
createCounterAsync / createMapAsync + root.setAsync(key, ...) (two steps, free-floating object linked afterwards) root.set(key, LiveMapValue.of(LiveCounter.create())) — create + link in one published operation
counter.incrementAsync(n, callback) wrapped in incrementCoroutine counterPath.increment(n) called directly (returns CompletableFuture)
map.setAsync / removeAsync wrapped in setCoroutine / removeCoroutine mapPath.set(key, value) / mapPath.remove(key) called directly
counter.subscribe(listener) + separate root-map subscription watching for the key being UPDATED, to rebind after "Reset all" replaces the counter One path subscription — a PathObject references a location and re-resolves per call, so it follows object replacement automatically
LiveMapValue.asString on map entries (throws on non-string) pathObject.asString().value() (null-safe; non-string entries are skipped)

Design notes

  • Location semantics over identity. Per the PathObject concept docs, stored PathObject references 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-bound Instance API isn't needed anywhere in this app; Utils.kt's file-level KDoc documents the distinction for readers using it as reference code.
  • Fire-and-forget mutations. Button handlers call the CompletableFuture-returning mutators directly without awaiting; the path subscription refreshes Compose state when the operation is acknowledged. This removed all scope.launch wrappers from the screens. Failures are dropped silently — same observable behaviour as the old app's swallow-all wrapper, and equally demo-only.
  • Awaits kept where ordering matters: getOrCreateCounter/getOrCreateMap await the create-and-link before binding (so vote buttons enable only once the counter exists), and the root fetch awaits sync.

Testing

  • :examples:assembleDebug and :examples:compileDebugAndroidTestKotlin green.
  • Manually verified on an emulator: voting increments live, "Reset all" zeroes counters on all clients, tasks add/edit/delete sync across devices.
  • Instrumentation tests (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

  • Bug Fixes
    • Improved real-time updates for counters, maps, and task lists so the UI refreshes more reliably when values change.
    • Simplified vote, reset, add, save, and delete actions to respond immediately without extra waiting.
  • Refactor
    • Updated the example app to use a newer path-based live data approach across the color voting and task management screens.

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>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sacOO7, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 17a5e381-e0a6-4524-8fbb-94321eb73303

📥 Commits

Reviewing files that changed from the base of the PR and between ba9934e and 4a173c8.

📒 Files selected for processing (4)
  • examples/src/main/kotlin/com/ably/example/Utils.kt
  • examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt
  • examples/src/main/kotlin/com/ably/example/screen/ObjectsSyncStatus.kt
  • examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt

Walkthrough

The example app's LiveObjects utility layer is migrated from the coroutine-based Channel.objects/LiveMap/LiveCounter API to the typed path-based LiveMapPathObject/LiveCounterPathObject API. Consuming screens (ColorVotingScreen, TaskManagementScreen) are updated to use the new function signatures and replace suspend/coroutine calls with direct fire-and-forget calls.

Changes

Example app path-based LiveObjects migration

Layer / File(s) Summary
Path-based LiveObjects utilities
examples/src/main/kotlin/com/ably/example/Utils.kt
getOrCreateCounter, getOrCreateMap, observeCounter, CounterState, observeMap, and observeRootObject are reimplemented on LiveMapPathObject/LiveCounterPathObject, replacing prior coroutine extension helpers and Channel.objects usage.
ColorVotingScreen vote/reset wiring
examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt
Updates observeCounter/observeRootObject call shapes, replaces coroutine-launched incrementCoroutine calls with direct increment(1), and replaces coroutine-based reset with direct resetRed/resetBlue/resetGreen calls.
TaskManagementScreen task mutation wiring
examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt
Updates imports and observeMap call shape, and replaces setCoroutine/removeCoroutine in Add Task, Remove All, Save, and Delete handlers with direct set/remove calls.

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(...)
Loading

Possibly related PRs

  • ably/ably-java#1216: Introduces ChannelBase.object/RealtimeObject.get() returning LiveMapPathObject, which this PR's observeRootObject migration directly depends on.

Poem

A rabbit hopped through paths anew,
No more coroutines to chase and chew,
Counters click and maps now set,
Direct and fast, without regret,
Hop, hop, hooray — the code's fresh view! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: migrating the examples app to the path-based LiveObjects API.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/update-liveobjects-example-app

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot temporarily deployed to staging/pull/1225/features July 3, 2026 11:00 Inactive
@sacOO7 sacOO7 changed the title chore(examples): migrate LiveObjects example app to the path-based API [AIT-1038] chore(examples): migrate LiveObjects example app to the path-based API Jul 3, 2026
@sacOO7 sacOO7 requested a review from Copilot July 3, 2026 11:01
@github-actions github-actions Bot temporarily deployed to staging/pull/1225/javadoc July 3, 2026 11:01 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 CompletableFuture usage (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.

Comment thread examples/src/main/kotlin/com/ably/example/Utils.kt
Comment thread examples/src/main/kotlin/com/ably/example/Utils.kt
Comment thread examples/src/main/kotlin/com/ably/example/Utils.kt Outdated
Comment thread examples/src/main/kotlin/com/ably/example/Utils.kt Outdated
Comment thread examples/src/main/kotlin/com/ably/example/Utils.kt
Comment thread examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Bind the Red card enable state to redCounter

enabled = greenCounter != null makes the Red button depend on the Green handle, so it can be disabled when Red is ready and enabled when Red is not. Use redCounter != null here 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

📥 Commits

Reviewing files that changed from the base of the PR and between b50f904 and ba9934e.

📒 Files selected for processing (3)
  • examples/src/main/kotlin/com/ably/example/Utils.kt
  • examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt
  • examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt

Comment thread examples/src/main/kotlin/com/ably/example/Utils.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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants