Home

Awesome

<div align="center"> <img alt="flutter ios 16 live activities" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/logo.webp" /> </div> <br /> <div align="center" style="display: flex;align-items: center;justify-content: center;"> <a href="https://pub.dev/packages/live_activities"><img src="https://img.shields.io/pub/points/live_activities?style=for-the-badge" style="margin-right: 10px" /></a> <a href="https://pub.dev/packages/live_activities"><img src="https://img.shields.io/pub/likes/live_activities?style=for-the-badge" style="margin-right: 10px" /></a> <a href="https://pub.dev/packages/live_activities"><img src="https://img.shields.io/pub/popularity/live_activities?style=for-the-badge" style="margin-right: 10px" /></a> <a href="https://pub.dev/packages/live_activities"><img src="https://img.shields.io/pub/v/live_activities?style=for-the-badge" style="margin-right: 10px" /></a> <a href="https://github.com/istornz/live_activities"><img src="https://img.shields.io/github/stars/istornz/live_activities?style=for-the-badge" /></a> </div> <br /> <div align="center"> <a href="https://radion-app.com" target="_blank" alt="Radion - Ultimate gaming app"> <img src="https://raw.githubusercontent.com/istornz/live_activities/main/images/radion.webp" width="600px" alt="Radion banner - Ultimate gaming app" /> </a> </div> <br />

A Flutter plugin to use iOS 16.1+ Live Activities & iPhone 14 Pro Dynamic Island features.

🧐 What is it ?

This plugin uses the iOS ActivityKit API.

live_activities can be used to show dynamic live notification & implement dynamic island feature on iPhones that support it 🏝️

⚠️ live_activities is only intended to use with iOS 16.1+ ! It will simply do nothing on other platform & < iOS 16.1

<br /> <div align="center" style="display: flex;align-items: center;justify-content: center;"> <div align="center" style="display: flex;flex-direction: column; align-items: center;justify-content: center;margin-right: 20px"> <img alt="flutter ios 16 live activities dynamic island" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/showcase/static/dynamic_island.webp" width="300px" style="margin-bottom: 20px" /> <img alt="flutter ios 16 live activities lockscreen" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/showcase/static/lockscreen_live_activity.webp" width="300px" /> </div> <img alt="flutter ios 16 live activities preview dynamic island" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/showcase/animations/create_live_activity.webp" width="250px" style="margin-right: 20px" /> <img alt="flutter ios 16 live activities preview action" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/showcase/animations/update_live_activity.webp" width="250px" style="margin-right: 20px" /> </div> <br />

👻 Getting started

Due to technical restriction, it's not currently possible to only use Flutter 🫣.

You need to implement in your Flutter iOS project a Widget Extension & develop in Swift/Objective-C your own Live Activity / Dynamic Island design.

ℹ️ You can check into the example repository for a full example app using Live Activities & Dynamic Island

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

ℹ️ You can check on this resource or here for more native informations.

struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
  public typealias LiveDeliveryData = ContentState // don't forget to add this line, otherwise, live activity will not display it.

  public struct ContentState: Codable, Hashable { }

  var id = UUID()
}
extension LiveActivitiesAppAttributes {
  func prefixedKey(_ key: String) -> String {
    return "\(id)_\(key)"
  }
}
// Create shared default with custom group
let sharedDefault = UserDefaults(suiteName: "YOUR_GROUP_ID")!

struct FootballMatchApp: Widget {
  var body: some WidgetConfiguration {
    ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
      // create your live activity widget extension here
      // to access Flutter properties:
      let myVariableFromFlutter = sharedDefault.string(forKey: context.attributes.prefixedKey("myVariableFromFlutter"))!

      // [...]
    }
  }
}

Access Flutter basic data from Native 🧵

let sharedDefault = UserDefaults(suiteName: "YOUR_GROUP_ID")!

⚠️ Be sure to use the SAME group id in your Swift extension and your Flutter app!

let pizzaName = sharedDefault.string(forKey: context.attributes.prefixedKey("name"))! // put the same key as your Dart map
let pizzaPrice = sharedDefault.float(forKey: context.attributes.prefixedKey("price"))
let quantity = sharedDefault.integer(forKey: context.attributes.prefixedKey("quantity"))
// [...]

Access Flutter files like pictures from Native 🧵

You can use the factory .image() to use options like resizing image.

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
    )
  ),
};

_liveActivitiesPlugin.createActivity(activityModel);

ℹ️ Use LiveActivityFileFromAsset to load a file from your Flutter asset.

ℹ️ Use LiveActivityFileFromUrl to load a file from an external url.

ℹ️ Use LiveActivityFileFromMemory to load a file from the memory (from a Uint8List object).

⚠️ File like picture need to be in a small resolution to be displayed in your live activity/dynamic island, you can use resizeFactor to automatically resize the image 👍.

if let assetImage = sharedDefault.string(forKey: context.attributes.prefixedKey("assetKey")), // <-- Put your key here
  let uiImage = UIImage(contentsOfFile: shop) {
  Image(uiImage: uiImage)
      .resizable()
      .frame(width: 53, height: 53)
      .cornerRadius(13)
} else {
  Text("Loading")
}
let ruleFile = sharedDefault.string(forKey: context.attributes.prefixedKey("txtFile"))! // <-- Put your key here
let rule = (try? String(contentsOfFile: ruleFile, encoding: .utf8)) ?? ""

// [...] display the rule txt string variable here

Communicate between native 🧵 and Flutter 💙

In order to pass some useful data between your native live activity / dynamic island with your Flutter app you just need to setup URL scheme.

⚠️ It's recommended to set a custom scheme, in this example, la is used but keep in mind, you should use a more personalized scheme.

ex: for an app named Strava, you could use str.

<img alt="add url scheme xcode" src="https://raw.githubusercontent.com/istornz/live_activities/main/images/tutorial/url_scheme.webp" width="700px" />
Link(destination: URL(string: "la://my.app/order?=123")!) { // Replace "la" with your scheme
  Text("See order")
}

⚠️ Don't forget to put the URL Scheme you have typed in the previous step. (str:// in the previous example)

_liveActivitiesPlugin.init(
  appGroupId: 'your.group.id', // replace here with your custom app group id
  urlScheme: 'str' // replace here with your custom app scheme
);
_liveActivitiesPlugin.urlSchemeStream().listen((schemeData) {
  // do what do you want here 🤗
});

Update Live Activity with push notification 🎯

You can update live activity directly in your app using the updateActivity() method, but if your app was killed or in the background, you can’t update the notification...

To do this, you can update it using Push Notification on a server.

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.

That's it 😇

<br />

📘 Documentation

NameDescriptionReturned 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 activityString The activity identifier
.createOrUpdateActivity()Create or updates an (existing) live activity based on the provided UUID via customIdString The activity identifier
.updateActivity()Update the live activity data by using the activityId providedFuture When the activity was updated
.endActivity()End the live activity by using the activityId providedFuture When the activity was ended
.getAllActivitiesIds()Get all activities ids createdFuture<List<String>> List of all activities ids
.getAllActivities()Get a Map of activitiyIds and the ActivityStateFuture<Map<String, LiveActivityState>> Map of all activitiyId -> LiveActivityState
.endAllActivities()End all live activities of the appFuture When all activities was ended
.areActivitiesEnabled()Check if live activities feature are supported & enabledFuture<bool> Live activities supported or not
.getActivityState()Get the activity current stateFuture<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 picturesFuture Picture removed
.activityUpdateStreamGet notified with a stream about live activity push token & statusStream<ActivityUpdate> Status updates for new push tokens or when the activity ends
<br />

🤔 Questions

Do I have to code in Swift?

Yes you need to implement your activity in Swift but no worries, there is a lot of cool tutorials:

I have an issue when building my app on iOS: Error (Xcode): Cycle inside Runner; building could produce unreliable results.

This error occurs due to a build script ordering issue. Follow this guide to resolve it.

I can't see my live activity when I create it...

It can be related to multiple issues, please be sure to:

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

Contributions are welcome. Contribute by creating a PR or an issue 🎉.

🎯 Roadmap