feat(live-activities): expose Live Activities through CustomerIO.liveActivities - #617
Draft
mahmoud-elmorabea wants to merge 12 commits into
Draft
feat(live-activities): expose Live Activities through CustomerIO.liveActivities#617mahmoud-elmorabea wants to merge 12 commits into
mahmoud-elmorabea wants to merge 12 commits into
Conversation
…K module Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Sample app builds 📱Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.
|
This was referenced Jul 23, 2026
mahmoud-elmorabea
changed the base branch from
main
to
feature/live-notifications
July 23, 2026 06:03
…to liveNotifications Follows the native iOS change that makes Live Activities a registerable CustomerIOModule reached via CustomerIO.liveActivities. - iOS: replace the held static module + initializeModule(from:) with module(from:) registered through sdkConfigBuilder.addModule, mirroring NativeLocation. Enables push-to-start on every init path. - iOS: a nil start result (module unregistered / type not enabled) becomes a rejected promise instead of a crash; unknown types fail softly. - iOS: reportDeepLinkOpen -> handleWidgetUrl, returning the redirect URL. - Android: migrate to the dedicated CustomerIOLiveNotificationsCallback and setLiveNotificationCallback; createLiveNotification no longer lives on CustomerIOPushNotificationCallback. - Config: `liveActivities` -> `liveNotifications`, `templates` -> `types`, and types are now the reverse-DNS identifiers shared by both native SDKs and the wire format. Unrecognized identifiers are ignored. - Branding: add logoResource (bundled drawable), preferred over logoUrl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ending an activity left the last content-state on screen — a Segments activity ended at "3 of 4" and stayed there. ActivityKit keeps the previous content when `Activity.end` is given none, and the bridge never passed one even though the native handle has always accepted it. `end` now takes an optional payload, mirroring `update`, so callers can show a terminal state (e.g. all segments complete). iOS-only by nature: Android's `endLiveNotification(activityId)` takes no final state and renders its own terminal presentation (the notification stops being ongoing), which is why Android already looked correct. The Android bridge accepts the argument for signature parity and ignores it, the same way branding is Android-only and ignored on iOS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TEMPORARY (REL-1). Live Notifications don't exist in any released native SDK, so CI cannot build these branches against published versions. - Android: pin the `live-notifications-review-fixes-SNAPSHOT` branch snapshot and add the Sonatype snapshots repository to resolve it. - iOS: bump the native pin to 4.6.0, matching the version the Live Activities podspecs declare on the native branch. The sample Podfile supplies those pods via a :git/:branch override, since the LA subspecs are not on trunk yet. Revert all of this — snapshot repository, snapshot pin, and Podfile overrides — once Live Notifications ships in released native SDKs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerates the api-extractor report, which had no Live Activities entries.
Also fixes what the report surfaced: `{@link start}` and `{@link logoUrl}` were
unqualified and unresolvable, and LiveActivityTemplate, LiveActivitySegmentsPayload
and LiveActivityCountdownTimerPayload were missing `@public` — the `@public` in the
file header sat on a detached comment block and applied to nothing, so it is removed.
Formats storage.ts, whose import was collapsed past the 80-char print width.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…odule log `update` rejected on an id this process didn't start while `end` resolved, and Android resolves for both because it routes the id straight to the native SDK. "Unknown" is not proof of caller error either: the map is process-local, so an activity started before a restart, or started by a push, legitimately isn't in it. Both now resolve and log instead. The push-module lookup used runCatching around an `as?` cast, which yields null rather than throwing, so its onFailure branch was unreachable and the "push module is not initialized" error never reached anyone. Replaced with an explicit null check. Renames the Kotlin config parameter to liveNotificationsConfig and corrects a KDoc that named a `liveActivities` config key that does not exist: config and wire vocabulary is liveNotifications, the API accessor stays liveActivities. Documents why Live Activities registration is unconditional on Android, unlike the Location module beside it, and that customTypes is Android-only in practice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forwarding opened URLs to handleWidgetUrl is mandatory on iOS or Live Activity taps go unattributed, and nothing in the SDK can do it for the host. Android needs no equivalent, and the Expo config plugin injects it automatically — so this is the one platform where an integrator has to act, and it was undocumented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Custom activity types were Android-only in practice and start-only. iOS rejected them outright, because ActivityKit needs a concrete Swift type to register an activity and observe its push-to-start token, and a metatype cannot cross a bridge. The native SDK now ships CIOCustomAttributes, whose content is an untyped map, so the SDK owns the Swift type and JavaScript supplies the values. Naming the type via `liveNotifications.customType` registers it under the app's own reverse-DNS identifier, which means iOS custom activities get push-to-start and metrics for the first time, and Android and iOS finally share one code path. `customType` is singular because an activity's reported type is resolved from its attributes type: registering the one type twice would attribute every event to whichever identifier came first, so the limit is expressed in the API rather than left as a silent mis-attribution. Removes `startCustom`. `start`/`update`/`end` with a LiveActivityTemplate.Custom payload replace it and, unlike it, work on both platforms and support updates. Android routes those through the native map overloads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…idn't start `update` was made to resolve on an unknown id, on the reasoning that the id might belong to a push-started activity legitimately absent from the process-local map. That reasoning was wrong: the bridge exposes no listener, so an id only ever reaches JavaScript as the return value of `start` and a push-started id can never be passed back in. What remains is an update that did not happen being reported as success — and Android performs it in the same situation, since it routes the id straight to the native SDK. Resolving here made one call return success on both platforms while only one of them did anything. `end` keeps resolving: re-ending an already-ended activity is a legitimate no-op. Nothing about an update that never applied is idempotent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Android returned a real activity id for a type that was never enabled. The native start call mints one regardless, and the handler then drops the notification at debug level, so from JavaScript the call looked like it worked and nothing rendered. It now rejects with the same code and message iOS already used. `customActivityType` was only assigned when `customType` was present, so a re-initialize that dropped it kept the stale value. Android would then start under an identifier no longer allowlisted — straight into the silent drop above. Both platforms now assign unconditionally. iOS coerced missing required fields to "" and 0 while Android rejected them, so an untyped caller got a blank card on one platform and an error on the other. iOS now rejects too, naming the field. `end` removed the handle before awaiting, so a throw lost it and the retry took the unknown-id path and reported success for an activity still on screen. It now forgets the handle only once the end succeeds. Drops the `withNativeModule` wrapper: the module is registered unconditionally on both platforms, so its null branch and __DEV__ warning were unreachable and its comment wrong. `getEnforcing` matches every other module here except Location, which really can be absent. `types` no longer accepts `Custom`, which both platforms silently dropped, and the custom `data` doc now states that nested values are unsupported and diverge. Also: import order, and the README snippet was missing `import customerio_reactnative` and the subspec prerequisite that the sample app has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r have seen The previous commit recorded the enabled activity types while applying the wrapper's own config, then rejected any start whose type wasn't in that set. That holds only when the SDK config is built from JavaScript. It isn't always: the Expo config plugin generates native code that calls `.enableLiveNotificationTypes(...)` straight on the app's config builder, so the wrapper's hook never runs, the recorded set stays empty, and every Android start would have been rejected — built-in templates included. Android now reads `SDKComponent.pushModuleConfig.liveNotificationTypes`, which is authoritative whoever built the config, and derives the custom identifier from it when the wrapper didn't see one: with a single custom type it is the one enabled identifier no built-in LiveNotificationType claims. iOS drops the pre-check for the same reason and lets the SDK answer — a nil handle already means "not registered", on every initialization path. That leaves the stored identifier unread, so it is removed. Caching what the wrapper happened to observe was the mistake; asking the SDK is both shorter and correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e branch The per-PR branch this snapshot was named after was deleted when it merged, so its artifact is frozen. The base feature branch it merged into publishes a snapshot from the same open PR and now carries the review-fix API this wrapper calls.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exposes Live Activities (iOS) / Live Notifications (Android) on the React Native SDK, consuming the native SDK-managed module so registration happens at SDK init.
Config lives under
liveNotifications—typesare the reverse-DNS identifiers shared with both native SDKs; unrecognized ones are ignored.endoptionally takes a final content-state so iOS can render a terminal card.Depends on customerio-ios#1178.