A Flutter plugin to use iOS 16.1+ Live Activities & iPhone 14 Pro Dynamic Island features.
This plugin use iOS ActivityKit API.
live_activities can be used to show dynamic live notification & implement dynamic island feature on the iPhone 14 Pro / Max ποΈ
β οΈ live_activities is only intended to use with iOS 16.1+ ! It will simply do nothing on other platform & < iOS 16.1
Due to some 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
-
- Open the Xcode workspace project
ios/Runner.xcworkspace
. - Click on
File
->New
->Target...
- Select
Widget Extension
& click on Next. - Specify the product name (MyAppWidget for eg.) & 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.
- Select
- Open the Xcode workspace project
- Enable push notification capabilities on the main
Runner
app only!.
- Enable live activity by adding this line in
Info.plist
for bothRunner
& yourWidget Extension
.
<key>NSSupportsLiveActivities</key>
<true/>
- Create App Group for both
Runner
& yourWidget Extension
.
βΉοΈ You can check on this resource or here for more native informations.
- In your extension, you need to create an
ActivityAttributes
called EXACTLYLiveActivitiesAppAttributes
(if you rename, activity will be created but not appear!)
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
public struct ContentState: Codable, Hashable { }
var id = UUID()
}
- Create an
UserDefaults
with your group id to access Flutter data in your Swift code.
// 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: "myVariableFromFlutter")!
// [...]
}
}
}
-
- Import the plugin.
import 'package:live_activities/live_activities.dart';
- Initialize the Plugin by passing the created App Group Id (created above).
final _liveActivitiesPlugin = LiveActivities(); _liveActivitiesPlugin.init(appGroupId: "YOUR_CREATED_APP_ID");
- Create your dynamic activity.
final Map<String, dynamic> activityModel = { 'name': 'Margherita', 'ingredient': 'tomato, mozzarella, basil', 'quantity': 1, }; _liveActivitiesPlugin.createActivity(activityModel);
You can pass all type of data you want but keep it mind it should be compatible with
UserDefaults
- In your Swift extension, you need to create an
UserDefaults
instance to access data:
let sharedDefault = UserDefaults(suiteName: "YOUR_CREATED_APP_ID")!
β οΈ Be sure to use the SAME group id in your Swift extension and your Flutter app!
- Access to your typed data:
let pizzaName = sharedDefault.string(forKey: "name")! // put the same key as your Dart map
let pizzaPrice = sharedDefault.float(forKey: "price")
let quantity = sharedDefault.integer(forKey: "quantity")
// [...]
- In your map, send a
LiveActivityImageFromAsset
orLiveActivityImageFromUrl
object:
final Map<String, dynamic> activityModel = {
'assetKey': LiveActivityImageFromAsset('assets/images/pizza_chorizo.png'),
'url': LiveActivityImageFromUrl(
'https://cdn.pixabay.com/photo/2015/10/01/17/17/car-967387__480.png',
resizeFactor: 0.3,
),
};
_liveActivitiesPlugin.createActivity(activityModel);
βΉοΈ Use LiveActivityImageFromAsset
to load an image from your Flutter asset.
βΉοΈ Use LiveActivityImageFromUrl
to load an image from an external url.
β οΈ Image need to be in a small resolution to be displayed in your live activity/dynamic island, you can useresizeFactor
to automatically resize the image π.
- In your Swift extension, display the image:
if let assetImage = sharedDefault.string(forKey: "assetKey"), // <-- Put your key here
let uiImage = UIImage(contentsOfFile: shop) {
Image(uiImage: uiImage)
.resizable()
.frame(width: 53, height: 53)
.cornerRadius(13)
} else {
Text("Loading")
}
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.
- Add a custom url scheme in Xcode by navigating to Runner > Runner > URL Types > URL Schemes
- In your Swift code, just create a new link and open to your custom URL Scheme
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.
- In your Flutter App, you just need to listen on the url scheme Scheme
_liveActivitiesPlugin.urlSchemeStream().listen((schemeData) {
// do what do you want here π€
});
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 |
.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 |
.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 ) |
.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 |
Contributions are welcome. Contribute by creating a PR or create an issue π.
- Inject a Widget inside the notification with Flutter Engine ?
- Pass media between extension & Flutter app.
- Support multiple type instead of
String
(Date, Number etc.). - Pass data across native dynamic island and Flutter app.
- Pass data across native live activity notification and Flutter app.
- Cancel all activities.
- Get all activities ids.
- Check if live activities are supported.