Cross-platform Live Activity SDK setup

Guided implementation of a Live Activity for Wrapper SDK's to allow for Push To Start and use of the Default Live Activity Type.

Since Live Activities are both relatively new and specific to the iOS platform, there is not a lot of support built into the non-native tools to build apps like React Native, Flutter, Unity, Cordova, etc. Since Live Activities must be implemented within native iOS, we have added additional functionality to the SDK to minimize as much interaction with native code as possible.

Requirements

  • An iOS / iPadOS Application (Live Activities are only available on the iPhone and iPad).
  • Ensure you are using a .p8 key (p12 certificates are not allowed with Live Activities). See our guide, iOS: Establishing a Token-Based Connection to APNs (.p8 Key), if migrating away from a .p12 certificate.
  • Live Activities are available on iOS 16.1+ and iPadOS 17+.
  • Xcode 14 or higher. While our SDK will handle the majority of the code, you will still need Xcode 14 and higher to be able to customize the UI of your Live Activity.
  • The latest version of our SDK.

Setup

1. Setup our SDK

Ensure that you've set up the most recent version of our Mobile SDK on your app. Live Activities are not available for websites or with our Web SDK.

Cross-Platform SDKs

2. Add the new setupDefault method

In order to tell the OneSignal SDK to manage the LiveActivity lifecycle for the DefaultLiveActivityAttributes type, we can now call the new setupDefault method. When calling this method, you can use both the Start Live Activity and Update Live Activity APIs to start/update/end the Default Live Activity.

import { OneSignal } from 'react-native-onesignal'

//Push To Start
OneSignal.LiveActivities.setupDefault()

//Launching the Live Activity from within the app (not needed for push to start)
const activityId = "my_activity_id"
const attributes = { title: "Sample Title" } ;
const content = { message: { en: "message" } };
OneSignal.LiveActivities.startDefault(activityId, attributes, content);


using OneSignalSDK;

//Push To Start
OneSignal.LiveActivities.SetupDefault();

//Launching the Live Activity from within the app (not needed for push to start)
string activityId = "my_activity_id";

OneSignal.LiveActivities.StartDefault(
  activityId,
  new Dictionary<string, object>() {
      { "title", "Welcome!" }
  },
  new Dictionary<string, object>() {
      { "message", new Dictionary<string, object>() {
          { "en", "Hello World!"}
      }},
  });

/*Cordova*/

//Push To Start
window.plugins.OneSignal.LiveActivities.setupDefault();

//Launching the Live Activity from within the app (not needed for push to start)
const activityId = "my_activity_id"
const attributes = {title: "Sample Title" };
const content = {message: { en: "message" } };
window.plugins.OneSignal.LiveActivities.startDefault(activityId, attributes, content);

/*Ionic/Capacitor*/
import OneSignal from 'onesignal-cordova-plugin'

//Push To Start
OneSignal.LiveActivities.setupDefault();

//Launching the Live Activity from within the app (not needed for push to start)
const activityId = "my_activity_id"
const attributes = { title: "Sample Title" };
const content = { message: { en: "message" } };
OneSignal.LiveActivities.startDefault(activityId, attributes, content);
using OneSignalSDK;

//Push To Start
OneSignal.LiveActivities.SetupDefault();

//Launching the Live Activity from within the app (not needed for push to start)
string activityId = "my_activity_id";

OneSignal.LiveActivities.StartDefault(
  activityId,
  new Dictionary<string, object>() {
      { "title", "Welcome!" }
  },
  new Dictionary<string, object>() {
      { "message", new Dictionary<string, object>() {
          { "en", "Hello World!"}
      }},
  });

import 'package:onesignal_flutter/onesignal_flutter.dart';

OneSignal.LiveActivities.setupDefault()

//Launching the Live Activity from within the app (not needed for push to start)
const String activityId = "my_activity_id";
OneSignal.LiveActivities.startDefault(activityId!, {
  "title": "Welcome!"
  }, {
  "message": {"en": "Hello World!"},
});

3. Create Activity Widget

  1. Open your main target Info.plist file in Xcode, add a new property, “Supports Live Activities”, and set it to “YES”.

  1. Select File > New > Target and search for “WidgetExtension” to create a new widget extension. Ensure “Include Live Activity” is checked. This will generate a few different files, the relevant files are WidgetExtensionBundle.swift, which identifies the widgets within the target, and WidgetExtensionLiveActivity.swift, which is the Live Activity widget.
  2. Select the WidgetExtensionLiveActivity.swift file and set its Target Membership to both the main target and “WidgetExtensionExtension”. The Runner target contains the code to start the Live Activity and must have visibility to WidgetExtensionAttributes.
  1. If you've followed the setup guides, you should have a folder titled "Pods" in your project that contains a Podfile. Inside this file, you will need to add the code below to the end of the Podfile. Run pod install --repo-update from your iOS folder after adding this target.
target 'OneSignalWidgetExtension' do
# use_frameworks! :linkage => :static
  pod 'OneSignalXCFramework', '>= 5.0.0', '< 6.0'
end

4. Layout the Live Activity UI

Update the contents of WidgetExtensionLiveActivity.swift with the following default Live Activity layout. The values in this Widget can be changed to whatever you'd like displayed on the widget, the setupDefault method will handle defining the struct for these attributes.

import ActivityKit
import WidgetKit
import SwiftUI
import OneSignalLiveActivities

//Your struct name might be different here
struct OneSignalWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: DefaultLiveActivityAttributes.self) { context in
            // Lock screen/banner UI goes here
            VStack {
                Spacer()
                Text("Title: " + (context.attributes.data["title"]?.asString() ?? "")).font(.headline)
                Spacer()
                HStack {
                    Spacer()
                    Text(context.state.data["message"]?.asDict()?["en"]?.asString() ?? "Default Message")
                    Spacer()
                }
                Text("INT: " + String(context.state.data["intValue"]?.asInt() ?? 0))
                Text("DBL: " + String(context.state.data["doubleValue"]?.asDouble() ?? 0.0))
                Text("BOL: " + String(context.state.data["boolValue"]?.asBool() ?? false))
                Spacer()
            }
            .activitySystemActionForegroundColor(.black)
            .activityBackgroundTint(.white)
        } dynamicIsland: { _ in
            DynamicIsland {
                // Expanded UI goes here.  Compose the expanded UI through
                // various regions, like leading/trailing/center/bottom
                DynamicIslandExpandedRegion(.leading) {
                    Text("Leading")
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Text("Trailing")
                }
                DynamicIslandExpandedRegion(.bottom) {
                    Text("Bottom")
                    // more content
                }
            } compactLeading: {
                Text("L")
            } compactTrailing: {
                Text("T")
            } minimal: {
                Text("Min")
            }
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }
    }
}

Test the Live Activity

  1. Start the app
  2. See all possible fields in our Live Activity API reference here. The structure of these fields might differ depending on how you've set up your UI. For example :
    1. "event_updates": This is the dynamic data that can be updated after the Live Activity has been started (anything after context.state in the code example). Since we have context.state.data we would add a data object to this field and any additional fields within like the message dictionary we have added in the code example. For usage, see example request below.
    2. "event_attributes": This is the static data that is set in the push to start request, and remains the same value until the Live Activity is removed or overwritten.
  3. When using push to start, you set the "activity_id" in the request, rather than in the code. Using different Activity ID's will start new Live Activities. Using the same Activity ID will overwrite the widget that is currently using that ID.
  4. Ensure that you've changed the OneSignal App ID in your url path, and the Rest API Key in the Authorization header. The DefaultActivityAttributes type cannot be changed if you are using the default setup. Please also note, the activity type added to your path is case sensitive and should match what is defined either by you or the Default activity used in the example below.
curl --request POST \
     --url https://onesignal.com/api/v1/apps/YOURAPPID/activities/activity/DefaultLiveActivityAttributes \
     --header 'Authorization: Basic YOUR_REST_API_KEY' \
     --header 'Content-Type: application/json' \
     --header 'accept: application/json' \
     --data '
{
  "event": "start",
  "event_updates": {
    "data": {
      "message": {
        "en": "The message is nested in context.state.data["message"] as a dictionary
       },
       "intValue": 10,
       "doubleValue": 3.14,
       "boolValue": true
    },
  "event_attributes": {
		"title": "this is set when the LA starts and does not get updated after"
    }
  },
  "activity_id": "my-activity-id",
  "name": "OneSignal Notification Name",
  "contents": {
    "en": "English Message"
  },
  "headings": {
    "en": "English Message"
  },
  "sound": "beep.wav",
  "priority": 10
}

Low-level methods

These are optional methods to use if you want to generate your own Push To Start token, which has also been added in the latest release of the SDK. Using these methods requires that you interact with Xcode more, and generate your own token for Push To Start with Swift. You can find a guide on this here.

//Setting the Push To Start Token
OneSignal.LiveActivities.setPushToStartToken(activityType: string, token: string)

//Removing the Push To Start Token
OneSignal.LiveActivities.removePushToStartToken(activityType: string)
//Setting the Push To Start Token
OneSignal.LiveActivities.SetPushToStartToken(string activityType, string token)

//Removing the Push To Start Token
OneSignal.LiveActivities.RemovePushToStartToken(string activityType)
/* Cordova */
//Setting the Push To Start Token
window.plugins.OneSignal.LiveActivities.setPushToStartToken(activityType: string, token: string)

//Removing the Push To Start Token
window.plugins.OneSignal.LiveActivities.removePushToStartToken(activityType: string)

/* Ionic */
//Setting the Push To Start Token
OneSignal.LiveActivities.setPushToStartToken(activityType: string, token: string)

//Removing the Push To Start Token
OneSignal.LiveActivities.removePushToStartToken(activityType: string)
//Setting the Push To Start Token
OneSignal.LiveActivities.SetPushToStartToken(string activityType, string token)

//Removing the Push To Start Token
OneSignal.LiveActivities.RemovePushToStartToken(string activityType)
//Setting the Push To Start Token
OneSignal.LiveActivities.setPushToStartToken(String activityType, String token)

//Removing the Push To Start Token
OneSignal.LiveActivities.removePushToStartToken(String activityType)