Live Activities developer setup

How to set up Live Activities on your iOS Mobile App with OneSignal

Live Activities is an iOS feature for displaying live information via a widget on the device lock screen and in the dynamic island. They are intended to display live data that changes during a time window, such as sports scores, delivery statuses, and other real-time transactional updates.

📘

Android Live Notifications

Android devices have a similar capability through Android Live Notifications.

Requirements

  • If you haven't already, follow our iOS SDK setup to add our Swift or Objective-C code into your app. If your app is written in another programming language, then follow Mobile SDK setup for our wrapper SDKs (React Native, Flutter, Unity, Cordova, etc.), then go to Cross-platform Live Activity SDK Setup to implement Live Activities.
  • You must have our iOS SDK 5.2.0 or higher to work with push-to-start. See our GitHub for more details.
  • 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.

Setup

These steps walk you through setting up Live Activities quickly. For more details, see Apple's Developer docs for Live Activities.

1. Add a Widget Extension

In Xcode, create a new Widget Extension target.

Go to File > New > Target and select Widget Extension.

Add a new widget extension target for your app in Xcode.

Add a new widget extension target for your app in Xcode.

Configure the Widget Extension by providing a name and ensure Include Live Activity is selected and click Finish.

1000

Widget extension options for a Live Activity.

You will be asked to activate the Live Activity scheme. Click Don't Activate when prompted.

2. Update Info.plist

Locate the Info.plist in the main target, add the key Supports Live Activities as Boolean, and set it to YES.

If you do not see this option, hover over "Information Property List" to reveal a "+" button, where you can then search for the “Supports Live Activities” key.

991

Adding support Live Activities key to Info list and setting its value to Yes

If you still do not see this property, ensure you are using Xcode 14 or higher.

This adds to the Info.plist file is as follows:

<key>NSSupportsLiveActivities</key>
<true/>

3. Define the static and dynamic data

After creating the Widget you will have a new folder that contains a "OneSignalWidgetLiveActivity" file. Open this to define the properties of the struct and to make changes to the widget UI.

The OneSignalLiveActivityAttributes protocol inherits from ActivityAttributes and describes the content that appears in your Live Activity. In addition to your widget-specific static attribute data and dynamic ContentState it also defines a static OneSignal attribute that contains metadata necessary for the OneSignal iOS SDK to manage the Live Activities lifecycle.

import ActivityKit
import WidgetKit
import SwiftUI
import OneSignalLiveActivities

struct OneSignalWidgetAttributes: OneSignalLiveActivityAttributes  {
    public struct ContentState: OneSignalLiveActivityContentState {
        
        // Dynamic stateful properties about your activity go here!
        var emoji: String
        var onesignal: OneSignalLiveActivityContentStateData?

    }
    // Fixed non-changing properties about your activity go here!
    var name: String
    var onesignal: OneSignalLiveActivityAttributeData
}

struct OneSignalWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: OneSignalWidgetAttributes.self) { context in
            // Lock screen/banner UI goes here
            VStack {
                Text("Hello \(context.attributes.name) \(context.state.emoji)")
            }
            .activityBackgroundTint(Color.cyan)
            .activitySystemActionForegroundColor(Color.black)

        } dynamicIsland: { context 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 \(context.state.emoji)")
                    // more content
                }
            } compactLeading: {
                Text("L")
            } compactTrailing: {
                Text("T \(context.state.emoji)")
            } minimal: {
                Text(context.state.emoji)
            }
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }
    }
}

Allow main target membership

Before moving to the next step, ensure that you allow target membership to your main target from the OneSignalWidgetLiveActivity file. Click the '+' button and type in the main target name to see the options under "choose target" and select the main target containing the Content View and your OneSignal initialization code.

4. Add the setup method to your AppDelegate

import OneSignalLiveActivities

/* This should be added after initializing the OneSignal SDK*/
if #available(iOS 16.1, *) {
	OneSignal.LiveActivities.setup(OneSignalWidgetAttributes.self)
}

5. Starting a Live Activity

There are 2 options to start a Live Activity on a device:

Push-to-start

If you've followed the steps above, you're setup to send a Push To Start request to start the Live Activity.

The path parameters are case-sensitive, and you'll want everything to match between the request, the attributes, and the widget. If anything is missing or incorrectly added, you may encounter issues when trying to launch the widget.

Here is an example request that will work for the example above:

curl --location 'https://api.onesignal.com/apps/YOUR_APP_ID/activities/activity/OneSignalWidgetAttributes' \
--header 'Authorization: Basic YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "event": "start",
    "activity_id": "my_activity_id",
    "included_segments": [
        "Subscribed Users"
    ],
    "event_attributes": {
        "name": "test"
    },
    "event_updates": {
        "emoji":"🤩"
    },
    "name": "test",
    "contents": {
        "en": "English Message"
    },
    "headings": {
        "en": "Engligh Message"
    }
}'

Launching the Live Activity from within the app (click-to-start)

This is not required as push-to-start will cover most cases where a Live Activity should be shown to the end user. If you want to launch the Live Activity from within the app, based on some event, you can follow these steps.

  1. Ensure that you have added the setup method in step 4, and have defined your widget attributes, before proceeding.
  2. Go to your ContentView.Swift and add a button that will be used to launch the Live Activity when clicked.
import SwiftUI
import ActivityKit
import OneSignalFramework
import OneSignalLiveActivities

struct ContentView: View {
  @StateObject private var viewModel = LiveActivityViewModel()

  var body: some View {
    VStack {
      Button("Start Live Activity") {
        viewModel.startLiveActivity()
      }
    }
  }
}

class LiveActivityViewModel: ObservableObject {

  func startLiveActivity() {
    let osAttributes = OneSignalLiveActivityAttributeData.create(activityId: "click_to_start")
    let attributes = OneSignalWidgetAttributes(name: "OneSignal", onesignal: osAttributes)
    let contentState = OneSignalWidgetAttributes.ContentState(emoji:"🤩", onesignal: nil)
    do {
        let activity = try Activity<OneSignalWidgetAttributes>.request(
            attributes: attributes,
            contentState: contentState,
            pushType: .token)
    } catch {
        print(error.localizedDescription)
    }
  }
}
  1. Open the app and try launching the activity using the button you've added.

6. Updating a Live Activity

Whether you've launched the Live Activity using push-to-start, or from within the app (click-to-start), you can now update the Live Activity for anyone that currently has that widget active on their device using the Update Live Activity endpoint.

The example request below will update the widget we launched with push-to start because it has the activity ID titled "my_activity_id" that we defined in the other API POST request. To update the activity started from the button click-to-start you would match whatever ID was used in in your ContentView, in the request path. For example: .../live_activities/click_to_start/...

curl --location 'https://api.onesignal.com/apps/YOUR_APP_ID/live_activities/my_activity_id/notifications' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Basic YOUR_API_KEY' \
--data '{
    "event": "update",
    "event_updates": {
        "emoji": "😎"
    },
    "contents": {
        "en": "Hello World"
    },
    "headings": {
        "en": "English Message"
    },
    "name": "Emoji Update",
}'

7. Ending a Live Activity

A Live Activity can be ended by using the same request above but using "end" for the value of the "event" parameter.

Setting the dismissal_date parameter will enable you to ensure that the activity is removed at a specific point in time on this request.

curl --location 'https://api.onesignal.com/apps/YOUR_APP_ID/live_activities/my_activity_id/notifications' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Basic YOUR_API_KEY' \
--data '{
    "event": "end",
    "event_updates": {
        "emoji": "👋"
    },
    "contents": {
        "en": "Goodbye!"
    },
    "headings": {
        "en": "English Message"
    },
    "name": "Emoji Update",
    "dismissal_date": "Thu Jan 01 1970 00:00:01 GMT+0000"
}'

Refer to our API documentation for more details. Other ways to end a Live Activity from the user side include:

  1. the user removing it by swiping the Live Activity away,
  2. or the user revoking permission for Live Activities from their iOS Settings.

FAQ

Is it possible to also read the Live Activity data from the payload on the main target?

Yes, it is possible to do so. This can be used for debugging or to update the app's UI every time a Live Activity update occurs.

Task {
    for await content in activity.contentUpdates {
        print("LA activity id: \(activity.id), content update: \(content.state)")
    }
}
// Example output:
// LA activity id: EFE6D54D-F7E1-45EF-9514-4C2598F8DED2, content update: ContentState(message: "My message from payload")

Getting lifecycle changes:

Task {
    for await state in activity.activityStateUpdates {
        print("LA state update: \(state)")
        //If you wanted to do something based on the state, you would use this:
      	if state != ActivityState.active {
            //Do something here
        }
    }
}

// Example output 1 - LA ended, but still visible
// LA state update: active

/* State can be equal to 4 possible values on the ActivityState enum
active, dismissed, ended, and stale
*/

📘

Troubleshooting

If running into issues, see our Mobile Troubleshooting Guide.

Try our example projects on our Github repository.

If stuck, contact support directly or email [email protected] for help.

For faster assistance, please provide:

  • Your OneSignal App ID
  • Details, logs, and/or screenshots of the issue.
  • Steps to reproduce