Skip to content

feat(sample): Live Activities demo screens - #369

Open
mahmoud-elmorabea wants to merge 15 commits into
live-activities-sdkfrom
live-activities-sample
Open

feat(sample): Live Activities demo screens#369
mahmoud-elmorabea wants to merge 15 commits into
live-activities-sdkfrom
live-activities-sample

Conversation

@mahmoud-elmorabea

@mahmoud-elmorabea mahmoud-elmorabea commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Demo screens for the two built-in templates and an app-owned custom type, plus AppDelegate routing for the opened metric.

Stacked on #368.


Note

Low Risk
Changes are confined to sample/testbed apps and reference integration patterns; no production SDK or auth paths are modified beyond optional temporary iOS dependency branch pins.

Overview
Adds end-to-end Live Activities / live notifications wiring to the flutter_sample_cocoapods and flutter_sample_spm testbeds so integrators can exercise built-in templates and a custom rideshare type.

On Dart (SPM sample), a new Live Activities screen drives CustomerIO.liveActivities start/update/end for segments, countdown, and custom payloads; SDK init gains LiveActivitiesConfig with template types plus customType for rideshare, with dashboard navigation and routing hooked up.

Android enables live activities via customerio_live_activities_enabled and registers RideshareLiveNotificationCallback in MainActivity.onCreate (before Flutter SDK init) to render the custom type and dismiss on end.

iOS adds a LiveActivityWidget extension (built-in CIOSegments / CIOCountdownTimer plus app-owned RideshareLiveActivity on CIOCustomAttributes), NSSupportsLiveActivities, and AppDelegate open url handling through CustomerIOLiveActivities.handleWidgetUrl for tap/opened attribution and deep links. CocoaPods opts into customer_io/liveactivities with temporary feat/live-activities git overrides; SPM pulls the same branch for widget package products. Fastlane lists the widget bundle ID for on-device signing.

Reviewed by Cursor Bugbot for commit be8b519. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
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.


@mahmoud-elmorabea mahmoud-elmorabea changed the title feat(live-activities): sample app demo screens + custom template feat(sample): Live Activities demo screens Jul 27, 2026
@mahmoud-elmorabea
mahmoud-elmorabea force-pushed the live-activities-sample branch 6 times, most recently from dd67534 to 6cc9583 Compare July 27, 2026 17:35
@mahmoud-elmorabea
mahmoud-elmorabea marked this pull request as ready for review July 28, 2026 06:31
@mahmoud-elmorabea
mahmoud-elmorabea requested a review from a team as a code owner July 28, 2026 06:31
Comment thread apps/flutter_sample_spm/lib/src/customer_io.dart

@Shahroz16 Shahroz16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I confirmed the existing Cursor thread about the SPM sample is practical: the SPM app owns the shared Dart screen but has none of the native iOS wiring and no Android custom callback. I am not duplicating that thread. I found one additional custom-widget gap below. Android custom tap and dismissal behavior is covered by the root SDK comment on Android #734.

} minimal: {
Image(systemName: "car.fill")
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we apply .cioWidgetUrl(context.state.cioMetadata) to both the lock-screen view and the DynamicIsland, like the bundled templates do? The AppDelegate routing in this PR is never reached for a Rideshare tap without that URL, so every server-pushed custom activity loses its opened metric and customer deep-link.

mahmoud-elmorabea and others added 13 commits July 28, 2026 14:07
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Return a terminal, non-ongoing notification on the end event so the
  custom rideshare notification can be dismissed instead of persisting.
- Format bridged numeric ETA as an integer (was rendering "5.0 min").
- End the current notification before re-issuing startCustom on update so
  Android replaces it in place instead of stacking/orphaning a second one.
- Show the section description above the action buttons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AppDelegate: handleWidgetUrl replaces reportDeepLinkOpen and returns the
  customer's redirect URL, which is what the app now routes.
- MainActivity: the custom rideshare renderer implements the dedicated
  CustomerIOLiveNotificationsCallback (createLiveNotification moved off
  CustomerIOPushNotificationCallback in the native Android SDK).
- Config: `templates` -> `types`.

Verified: sample APK builds against the local native Android SDK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cond module

Custom iOS activity types previously stood up their own LiveActivitiesModule
alongside the plugin's. With Live Activities now an SDK-managed module reached
through CustomerIO.liveActivities, a second registration is last-write-wins and
would clobber the built-in templates — and the static initialize entry point it
used is gone.

The app now requests the activity through ActivityKit and adopts it, so
update/end still report. `adopt` does not report `start`: that needs the type
registered on the SDK module, which only a Swift metatype can do and so cannot
be driven from Dart.

Verified: Runner + LiveActivityWidget.appex build for the simulator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ending Segments left the card frozen mid-progress; it now ends showing all
segments complete and "Delivered". Countdown ends without an endTime so it
reads as finished instead of counting down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TEMPORARY (REL-1). Adds a :git/:branch override so CocoaPods can find the Live
Activities pods, which are not on trunk yet. Remove once they ship.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The LiveActivityWidget target is in the Xcode project and its bundle imports
CioLiveActivities_Attributes/_Templates, but the Podfile declared no target for
it, so it linked nothing and failed to resolve those modules. The block was
written earlier and lost in a rebase; this restores it, retargeted at the branch
the Runner target already uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Live Activities are now opt-in on iOS, so the samples have to ask for them or they
silently lose the feature: the cocoapods app adds the `liveactivities` subspec, and
both apps set customerio_live_activities_enabled=true, which is what the SPM
Package.swift reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rideshare demo needed an app-owned native module and an `adopt` call on iOS, a
MethodChannel of its own, and a Platform branch in between. It now uses the same
start/update/end the built-in templates use, on both platforms.

Deletes what that made redundant: the RideshareAttributes type, the
SampleCustomLiveActivity handler, and their Xcode project entries. The widget renders
CIOCustomAttributes instead — the SDK owns the type, the app owns the view, which is the
whole point of the new custom template.

Android's "update" was ending the notification and starting a new one, justified by a
comment claiming the wrapper has no by-id update for custom types. It does now
(updateLiveNotification(activityId, activityType, data)); the workaround and the comment
are both gone.

Also fixes the config call the samples never caught up with: `liveActivitiesConfig` was
renamed `liveNotificationsConfig` in the SDK branch, so the sample app did not compile.

Verified: flutter analyze clean (2 pre-existing infos), and the cocoapods sample builds
for device with the LiveActivityWidget extension embedded, against native iOS commit
95255429 which carries CIOCustomAttributes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…se branch

The per-PR branch these pods were pinned to was deleted when it merged, so
CocoaPods can no longer resolve it.
CI builds this app without code signing, so nothing here is required for it to
pass. The identifier is listed for the same reason the notification-service one
is: running the sample on a device needs a development profile for every
embedded extension.
…CocoaPods one

The SPM sample shipped the demo screens and `liveNotificationsConfig` while the
native half landed only in the CocoaPods app, so it could not render an activity
on either platform and a tap reported nothing.

It now has the widget extension, the `handleWidgetUrl` route, and the Android
render callback. Two things were missing beyond the widget itself: the AppDelegate
never imported `customer_io`, and `Runner/Info.plist` had no
`NSSupportsLiveActivities` — without that key ActivityKit refuses to start an
activity at all, so the widget would have been inert even once it compiled.

Where the CocoaPods app declares the Live Activities pods in a widget Podfile
target, this one takes them as a remote Swift package pinned to the same
unreleased branch (REL-1: swap for a version requirement once they ship).

Also fixes the rideshare headline in both samples: it hardcoded "is on the way"
while the demo ends the activity with a terminal "Arrived" status.
`flutter build ios --release` failed with "Signing for LiveActivityWidget
requires a development team". Runner and the notification service carry empty
DEVELOPMENT_TEAM placeholders that fastlane fills in, and nothing fills one for a
target added outside the CocoaPods integration.

Matches the CocoaPods sample's widget, which disallows code signing for the same
reason. The extension is therefore unsigned in these builds — enough for CI,
which only verifies compilation, but not installable on a device.
Both samples' rideshare widgets rendered correctly but applied no
`.cioWidgetUrl`, so the AppDelegate route added in this PR was never reached for
a rideshare tap: every server-pushed custom activity lost its opened metric and
deep link. Applied to the lock screen and the Dynamic Island, matching where the
SDK's built-in templates put it.
Comment thread apps/flutter_sample_spm/lib/src/screens/live_activities.dart
The Android renderer hardcoded "is on the way" for any non-terminal event, so an
update that changed the status left the title stale while the body already showed
the new state — and it disagreed with the iOS widget, which now leads with
`status`. The title follows `status` too, and the driver moves into the body so
nothing is lost.

The demo screen also called `setState` after awaiting a native call and only
checked `mounted` afterwards, which guarded the snackbar but not the rebuild.
Leaving the route mid-call could assert.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit be8b519. Configure here.

// once the Flutter engine runs). The wrapper stores this statically and applies it onto the
// push module config during SDK init.
CustomerIOLiveActivities.setLiveNotificationCallback(RideshareLiveNotificationCallback())
super.onCreate(savedInstanceState)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CocoaPods sample missing Dart demo

Medium Severity

This PR wires Live Activities natively in the CocoaPods sample (widget extension, AppDelegate URL handling, Android rideshare renderer) but adds the demo screen and liveNotificationsConfig only under flutter_sample_spm. The CocoaPods app never registers template or custom types from Dart, so the native callback and routing added here cannot be exercised the way the SPM sample can.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit be8b519. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants