Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push to start live activities #1377

Merged
merged 42 commits into from
May 8, 2024
Merged

Conversation

brismithers
Copy link
Contributor

@brismithers brismithers commented Feb 27, 2024

Description

One Line Summary

Update SDK to support Live Activities PushToStart and add a concept of a "Default" Live Activity to facilitate easier wrapper SDK adoption.

Details

Push to Start Live Activities

Starting with iOS 17.2, Live Activities can now be started via push notification (Apple's documentation). This change enhances the OneSignal SDK to provide application's access to the full suite of Live Activity functionality.

Preferred method to setup Live Activities with OneSignal

A new function OneSignal.LiveActivities.setup has been created which allows OneSignal to manage the lifecycle of a LiveActivity on behalf of the application. This includes listening for both pushToStart token updates and pushToUpdate token updates. When using this method, the application does not need to listen for pushToStart token updates, the starting of a live activity, nor update token updates. A typical usage looks like this:

OneSignal.LiveActivities.setup(MyWidgetAttributes.self)

This method accepts any struct which adopts the OneSignalLiveActivityAttributes protocol. The OneSignalLiveActivityAttributes protocol establishes a high-level onesignal attribute and a high-level onesignal content state property which will be used internally by OneSignal. At a minimum, your attributes will have the following structure:

 struct MyWidgetAttributes: OneSignalLiveActivityAttributes {
     public struct ContentState: OneSignalLiveActivityContentState {
         // Your content state properties defined here as well...

         // A content state property reserved for OneSignal internal use.
         var onesignal: OneSignalLiveActivityContentStateData?
     }

     // Your attribute properties defined here as well...

     // An attribute property reserved for OneSignal internal use.
     var onesignal: OneSignalLiveActivityAttributeData
 }

If an application starts a live activity "in app" it no longer has to listen for token updates explicitly, starting a Live Activity "in-app" will look something like this:

 let oneSignalAttribute = OneSignalLiveActivityAttributeData.create(activityId: "my-activity-id")
 let attributes = MyWidgetAttributes(onesignal: oneSignalAttribute, ...otherWidgetAttributes)
 let contentState = MyWidgetAttributes.ContentState(widgetContentState)
 do {
     let _ = try Activity<MyWidgetAttributes>.request(
             attributes: attributes,
             contentState: contentState,
             pushType: .token)
 } catch let error {
     print(error.localizedDescription)
 }

Alternative (low level) method to setup Live Activities with OneSignal

If a customer app does not want to alter their ActivityAttribute structure, or wants more fine grained control over when OneSignal should manage a Live Activity's start/update tokens, lower-level SDK methods are provided. These methods require the application to listen for pushToStart token updates, the starting of live activities, and update token updates. This encompasses existing
functions OneSignal.LiveActivities.enter and OneSignal.LiveActivities.exit which are documented here. Additional new methods to cover registering/unregistering pushToStart tokens have been created:

  • OneSignal.LiveActivities.setPushToStartToken: To be called 'per-activity-type` each time that activity type's pushToStart token has been refreshed. The new pushToStart tokens are sync'd to the OneSignal backend and registered against the current subscription, where it can be the target of a started live activity. A typical usage looks like this:
if #available(iOS 17.2, *) {
  // Setup an async task to monitor and send pushToStartToken updates to OneSignalSDK.
  Task {
      for try await data in Activity<MyWidgetAttributes>.pushToStartTokenUpdates {
          let token = data.map {String(format: "%02x", $0)}.joined()
          OneSignal.LiveActivities.setPushToStartToken(MyWidgetAttributes.self, withToken: token)
      }
  }
  // Setup an async task to monitor for an activity to be started, for each started activity we
  // can then set up an async task to monitor and send updateToken updates to OneSignalSDK. If
  // there can be multiple instances of this activity-type, the activity-id (i.e. "my-activity-id") is
  // most likely passed down as an attribute within MyWidgetAttributes.
  Task {
      for await activity in Activity<MyWidgetAttributes>.activityUpdates {
        Task {
            for await pushToken in activity.pushTokenUpdates {
                let token = pushToken.map {String(format: "%02x", $0)}.joined()
                OneSignal.LiveActivities.enter("my-activity-id", withToken: token)
            }
        }
      }
  }
}
  • OneSignal.LiveActivities.removePushToStartToken: To be called per-activity-type whenever that activity type should no longer be registered against the current subscription. A typical usage looks like this:
OneSignal.LiveActivities.removePushToStartToken(MyWidgetAttributes.self)

Default Live Activity

The concept of a "Default" Live Activity has been established in the SDK, which eliminates the need for a customer app to define and manage their own ActivityAttributes. This is most beneficial for wrapper-SDKs, as they will no longer need to create their own cross-platform <-> native iOS bridge to establish this management. The following new external interfaces are provided for customer app's to use:

  • A DefaultLiveActivityAttributes struct which conforms to OneSignalLiveActivityAttributes (i.e. a OneSignal aware ActivityAttributes). A customer app can create a widget with ActivityConfiguration(for: DefaultLiveActivityAttributes.self) to use this provided model. The DefaultLiveActivityAttributes establishes a dynamic dictionary of attributes, able to consume any customer-provided structure of data.
  • A new function OneSignal.LiveActivities.setupDefault() which tells the OneSignal SDK to manage the LiveActivity lifecycle for the DefaultLiveActivityAttributes type. When calling this method, a customer can use both 'push-to-start' and push-to-update notifications to start/update/end their Default Live Activity.
  • A new function OneSignal.LiveActivities.startDefault(activityId, activityAttributes, initialContentState) which allows a customer app to start a live activity based on the DefaultLiveActivityAttributes type "in app".

The downside to using a default Live Activity is:

  • The app will be limited to only one Live Activity type.
  • The widget rendering logic is slightly more complicated in that it is using a dynamically defined ActivityAttributes. For example, instead of rendering a text attribute as Text(context.state.message) it would be rendered as Text(context.state.data["message"]?.asString() ?? "").

Motivation

Full support of Live Activities functionality introduced in iOS 17.2

Scope

Live Activities

Testing

Unit testing

  • Added some basic unit testing to ensure the public APIs will not regress from their initial definition.

Manual testing

  • Ran the iOS Example app and monitored log statements to ensure the pushToStart token and pushToUpdate token requests were being sent to the backend.
  • Ensured a pushToStart request "per-activity-type, per token" was only sent up once.
  • Ensured a pushToUpdate request "per-activity-id, per token" was only sent up once.
  • Ensured the start/update requests are removed after their TTL expires.

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes
  • Live Activities

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
    • If it is hard to explain how any codes changes are related to each other then it most likely needs to be more than one PR
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
    • Simplify with less code, followed by splitting up code into well named functions and variables, followed by adding comments to the code.
  • I have reviewed this PR myself, ensuring it meets each checklist item
    • WIP (Work In Progress) is ok, but explain what is still in progress and what you would like feedback on. Start the PR title with "WIP" to indicate this.

This change is Reviewable

@nan-li nan-li self-assigned this Feb 27, 2024
@brismithers brismithers marked this pull request as ready for review March 12, 2024 21:28
…t success/fail

* Fix issue where the request could have been removed from the cache by the time the http request success/fail runs
* Add ability to dismis any existing live activity with the same activityId
* Add more docs to public APIs
* Handle automatic removal of stale LA requests
* Refactor LA executor to be more readable
…ptions for more granular control.

* Do not retry LA reqeust when retryable failure until app restarts
@brismithers brismithers force-pushed the push-to-start-live-activities branch from f48f160 to b4b6d62 Compare March 13, 2024 16:41
brismithers and others added 11 commits April 9, 2024 13:06
* Add `OneSignalLiveActivities` to the OneSignalFramework
* Add `OneSignalLiveActivities` to the update_swift_package script
* Create concept of a built in "default" Live Activity widget attributes. The primary use is for cross-platform apps to more easily drive the setup and creation of a live activity.  Rather than have to create their own cross-platform<->iOS bridge, they can use the OneSignal SDK to handle it.

* Add tests and additional documentation, small tweaks

* Create objc bindings for methods/classes to be used by wrapper SDKs

* Add additional verbose logging to OneSignal.LiveActivities.setupXXX calls

* Move AnyCodable and DefaultLiveActivityAttributes under Source directory
* Its dependency XCTest changed from ios to always used.
Fixes spacing, non-logic items
* The switch statements are appropriate
@nan-li nan-li force-pushed the push-to-start-live-activities branch from 6cca5d7 to 98c38d5 Compare May 6, 2024 23:33
* So it knows of the pushToStart Apple API
@nan-li nan-li force-pushed the push-to-start-live-activities branch 5 times, most recently from 8886d1f to 7d681fc Compare May 7, 2024 16:58
* Update MockOSDispatchQueue to use dispatch queue under the covers
@nan-li nan-li force-pushed the push-to-start-live-activities branch from 44c8cd3 to 005a8c1 Compare May 7, 2024 18:30
@brismithers brismithers merged commit 0ba13bf into main May 8, 2024
3 of 5 checks passed
@brismithers brismithers deleted the push-to-start-live-activities branch May 8, 2024 20:51
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.

3 participants