Skip to content

Commit

Permalink
πŸ“ Add section about pushToStart
Browse files Browse the repository at this point in the history
  • Loading branch information
Clon1998 committed Mar 2, 2025
1 parent 1a0ed27 commit 520c7d0
Showing 1 changed file with 124 additions and 48 deletions.
172 changes: 124 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ You need to **implement** in your Flutter iOS project a **Widget Extension** & d
> ℹ️ You can check into the [**example repository**](https://github.com/istornz/live_activities/tree/main/example) for a full example app using Live Activities & Dynamic Island
- ## πŸ“± Native
- Open the Xcode workspace project `ios/Runner.xcworkspace`.
- Click on `File` -> `New` -> `Target...`
- Select `Widget Extension` & click on **Next**.
- Open the Xcode workspace project `ios/Runner.xcworkspace`.
- Click on `File` -> `New` -> `Target...`
- Select `Widget Extension` & click on **Next**.
- Specify the product name (e.g., `MyAppWidget`) and be sure to select "**Runner**" in "Embed in Application" dropdown.
- Click on **Finish**.
- When selecting Finish, an alert will appear, you will need to click on **Activate**.
- Click on **Finish**.
- When selecting Finish, an alert will appear, you will need to click on **Activate**.

<img alt="create widget extension xcode" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/tutorial/create_widget_extension.webp" width="700px" />

Expand Down Expand Up @@ -124,20 +124,20 @@ struct FootballMatchApp: Widget {

- ## πŸ’™ Flutter

- Import the plugin.
- Import the plugin.

```dart
import 'package:live_activities/live_activities.dart';
```

- Initialize the Plugin by passing the created **App Group Id** (created above).
- Initialize the Plugin by passing the created **App Group Id** (created above).

```dart
final _liveActivitiesPlugin = LiveActivities();
_liveActivitiesPlugin.init(appGroupId: "YOUR_GROUP_ID");
```

- Create your dynamic activity.
- Create your dynamic activity.

```dart
final Map<String, dynamic> activityModel = {
Expand Down Expand Up @@ -183,10 +183,10 @@ final Map<String, dynamic> activityModel = {
'txtFile': LiveActivityFileFromAsset('assets/files/rules.txt'),
'assetKey': LiveActivityFileFromAsset.image('assets/images/pizza_chorizo.png'),
'url': LiveActivityFileFromUrl.image(
'https://cdn.pixabay.com/photo/2015/10/01/17/17/car-967387__480.png',
imageOptions: LiveActivityImageFileOptions(
resizeFactor: 0.2
)
'https://cdn.pixabay.com/photo/2015/10/01/17/17/car-967387__480.png',
imageOptions: LiveActivityImageFileOptions(
resizeFactor: 0.2
)
),
};
Expand Down Expand Up @@ -270,24 +270,24 @@ You can update live activity directly in your app using the `updateActivity()` m
To do this, you can update it using Push Notification on a server.

- Get the push token:
- Listen on the activity updates (recommended):
```dart
_liveActivitiesPlugin.activityUpdateStream.listen((event) {
event.map(
active: (activity) {
// Get the token
print(activity.activityToken);
},
ended: (activity) {},
unknown: (activity) {},
);
});
```
- Get directly the push token (not recommended, because the token may change in the future):
```dart
final activityToken = await _liveActivitiesPlugin.getPushToken(_latestActivityId!);
print(activityToken);
```
- Listen on the activity updates (recommended):
```dart
_liveActivitiesPlugin.activityUpdateStream.listen((event) {
event.map(
active: (activity) {
// Get the token
print(activity.activityToken);
},
ended: (activity) {},
unknown: (activity) {},
);
});
```
- Get directly the push token (not recommended, because the token may change in the future):
```dart
final activityToken = await _liveActivitiesPlugin.getPushToken(_latestActivityId!);
print(activityToken);
```
- Update your activity with the token on your server (more information can be [**found here**](https://ohdarling88.medium.com/update-dynamic-island-and-live-activity-with-push-notification-38779803c145)).
To set `matchName` for a specific notification, you just need to grab the notification id you want (ex. `35253464632`) and concatenate with your key by adding a `_`, example: `35253464632_matchName`.
Expand All @@ -296,24 +296,100 @@ That's it πŸ˜‡
<br />
## Push-to-Start Live Activities (iOS 17.2+) πŸš€
iOS 17.2 introduces the ability
to [create Live Activities remotely](https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Start-new-Live-Activities-with-ActivityKit-push-notifications)
via push notifications before the user has even
opened your app. This is called "push-to-start" functionality.
### Check Support
First, check if the device supports push-to-start:
```dart
final isPushToStartSupported = await _liveActivitiesPlugin.allowsPushStart();
if (isPushToStartSupported) {
// Device supports push-to-start (iOS 17.2+)
}
```

### Listen for Push-to-Start Tokens

To use push-to-start, you need to listen for push-to-start tokens:

```dart
_liveActivitiesPlugin.pushToStartTokenUpdateStream.listen((token) {
// Send this token to your server
print('Received push-to-start token: $token');
// Your server can use this token to create a Live Activity
// without the user having to open your app first
});
```

### Server Implementation

On your server, you'll need to send a push notification with the following
payload [structure](https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Construct-the-payload-that-starts-a-Live-Activity):

```json
{
"aps": {
"timestamp": 1234,
"event": "start",
"content-state": {
"currentHealthLevel": 100,
"eventDescription": "Adventure has begun!"
},
"attributes-type": "AdventureAttributes",
"attributes": {
"currentHealthLevel": 100,
"eventDescription": "Adventure has begun!"
},
"alert": {
"title": {
"loc-key": "%@ is on an adventure!",
"loc-args": [
"Power Panda"
]
},
"body": {
"loc-key": "%@ found a sword!",
"loc-args": [
"Power Panda"
]
},
"sound": "chime.aiff"
}
}
}
```

The push notification should be sent to the push-to-start token you received from the pushToStartTokenUpdateStream. Your
server needs to use Apple's APNs with the appropriate authentication to deliver these notifications.

## πŸ“˜ Documentation

| Name | Description | Returned value |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `.init()` | Initialize the Plugin by providing an App Group Id (see above) | `Future` When the plugin is ready to create/update an activity |
| `.createActivity()` | Create an iOS live activity | `String` The activity identifier |
| `.createOrUpdateActivity()` | Create or updates an (existing) live activity based on the provided `UUID` via `customId` | `String` The activity identifier |
| `.updateActivity()` | Update the live activity data by using the `activityId` provided | `Future` When the activity was updated |
| `.endActivity()` | End the live activity by using the `activityId` provided | `Future` When the activity was ended |
| `.getAllActivitiesIds()` | Get all activities ids created | `Future<List<String>>` List of all activities ids |
| `.getAllActivities()` | Get a Map of activitiyIds and the `ActivityState` | `Future<Map<String, LiveActivityState>>` Map of all activitiyId -> `LiveActivityState` |
| `.endAllActivities()` | End all live activities of the app | `Future` When all activities was ended |
| `.areActivitiesEnabled()` | Check if live activities feature are supported & enabled | `Future<bool>` Live activities supported or not |
| `.getActivityState()` | Get the activity current state | `Future<LiveActivityState?>` An enum to know the status of the activity (`active`, `dismissed` or `ended`) |
| `.getPushToken()` | Get the activity push token synchronously (prefer using `activityUpdateStream` instead to keep push token up to date) | `String?` The activity push token (can be null) |
| `.urlSchemeStream()` | Subscription to handle every url scheme (ex: when the app is opened from a live activity / dynamic island button, you can pass data) | `Future<UrlSchemeData>` Url scheme data which handle `scheme` `url` `host` `path` `queryItems` |
| `.dispose()` | Remove all pictures passed in the AppGroups directory in the current session, you can use the `force` parameters to remove **all** pictures | `Future` Picture removed |
| `.activityUpdateStream` | Get notified with a stream about live activity push token & status | `Stream<ActivityUpdate>` Status updates for new push tokens or when the activity ends |
| Name | Description | Returned value |
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| `.init()` | Initialize the Plugin by providing an App Group Id (see above) | `Future` When the plugin is ready to create/update an activity |
| `.createActivity()` | Create an iOS live activity | `String` The activity identifier |
| `.createOrUpdateActivity()` | Create or updates an (existing) live activity based on the provided `UUID` via `customId` | `String` The activity identifier |
| `.updateActivity()` | Update the live activity data by using the `activityId` provided | `Future` When the activity was updated |
| `.endActivity()` | End the live activity by using the `activityId` provided | `Future` When the activity was ended |
| `.getAllActivitiesIds()` | Get all activities ids created | `Future<List<String>>` List of all activities ids |
| `.getAllActivities()` | Get a Map of activitiyIds and the `ActivityState` | `Future<Map<String, LiveActivityState>>` Map of all activitiyId -> `LiveActivityState` |
| `.endAllActivities()` | End all live activities of the app | `Future` When all activities was ended |
| `.areActivitiesEnabled()` | Check if live activities feature are supported & enabled | `Future<bool>` Live activities supported or not |
| `.allowsPushStart()` | Check if device supports push-to-start for Live Activities (iOS 17.2+) | `Future<bool>` Whether push-to-start is supported |
| `.getActivityState()` | Get the activity current state | `Future<LiveActivityState?>` An enum to know the status of the activity (`active`, `dismissed` or `ended`) |
| `.getPushToken()` | Get the activity push token synchronously (prefer using `activityUpdateStream` instead to keep push token up to date) | `String?` The activity push token (can be null) |
| `.urlSchemeStream()` | Subscription to handle every url scheme (ex: when the app is opened from a live activity / dynamic island button, you can pass data) | `Future<UrlSchemeData>` Url scheme data which handle `scheme` `url` `host` `path` `queryItems` |
| `.dispose()` | Remove all pictures passed in the AppGroups directory in the current session, you can use the `force` parameters to remove **all** pictures | `Future` Picture removed |
| `.activityUpdateStream` | Get notified with a stream about live activity push token & status | `Stream<ActivityUpdate>` Status updates for new push tokens or when the activity ends |
| `.pushToStartTokenUpdateStream` | Stream of push-to-start tokens for creating Live Activities remotely (iOS 17.2+) | `Stream<String>` Stream of tokens for push-to-start capability |

<br />

Expand Down Expand Up @@ -342,12 +418,12 @@ That's it πŸ˜‡
> - Supports Live Activities: Be sure to set the `NSSupportsLiveActivities` property to `true` in `Info.plist` files for **BOTH** `Runner` and your `extension`.
> - iOS Version Requirement: The device must run **iOS 16.1 or later**.
> - Device Activity Check: Confirm that the `areActivitiesEnabled()` method returns true on your device.
> - Minimum Deployment Target: Confirm that the `extensions` deployment target is not set lower than your devices.
> - Minimum Deployment Target: Confirm that the `extensions` deployment target is not set lower than your devices.
### Is Android supported?

> Currently, no, but the plugin does not crash when run on Android. This means you can safely install it in a hybrid app.
>
>
> Simply call `areActivitiesEnabled()` before creating your activity to ensure it can be displayed on the user's device. 😊
## πŸ‘₯ Contributions
Expand Down

0 comments on commit 520c7d0

Please sign in to comment.