Flutter Live Activities
Flutter plugin for Live Activities. Use to create, update and handling action for DynamicIsland UI
and Lock screen/banner UI
English | 中文说明
This plugin requires notification permission
1. Add a Widget to the iOS project
- Directory structure
2. Edit Runner/Info.plist
and live_activity_test/Info.plist
both add:
<plist version="1.0">
<dict>
...
<key>NSSupportsLiveActivities</key>
<true/>
...
</dict>
</plist>
3. Create a data channel in widget swift file
live_activity_test/live_activity_testLiveActivity.swift
import ActivityKit
import SwiftUI
import WidgetKit
// Custom data model
struct TestData {
var text: String
init?(JSONData data: [String: String]) {
self.text = data["text"] ?? ""
}
init(text: String) {
self.text = text
}
}
// Data channel <- Must!
struct FlutterLiveActivities: ActivityAttributes, Identifiable {
public typealias LiveData = ContentState
public struct ContentState: Codable, Hashable {
var data: [String: String]
}
var id = UUID()
}
@available(iOSApplicationExtension 16.1, *)
struct live_activity_testLiveActivity: Widget {
var body: some WidgetConfiguration {
// Binding
ActivityConfiguration(for: FlutterLiveActivities.self) { context in
// Lock screen/banner UI goes here
// Json to model
let data = TestData(JSONData: context.state.data)
// UI
VStack {
Text(data?.text ?? "")
}
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in
// Json to model
let data = TestData(JSONData: context.state.data)
// DynamicIsland
return 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) {
// Show data from flutter
Text(data?.text ?? "")
}
} compactLeading: {
Text("L")
} compactTrailing: {
Text("T")
} minimal: {
Text("Min")
}
.keylineTint(Color.red)
}
}
}
For more layout information, please refer to: live activities
4. APIs
import 'package:flutter_live_activities/flutter_live_activities.dart';
...
final FlutterLiveActivities _liveActivities = FlutterLiveActivities();
String? _activityId;
- Check if the Live Activities function is enabled
await _liveActivities.areActivitiesEnabled();
- Get launch url
await _liveActivities.getInitUri()
- Create a Live Activity
_activityId = await _liveActivities.createActivity(<String, String>{'text': 'Hello World'});
- Update a Live Activity
if(_activityId != null) {
await _liveActivities.updateActivity(_activityId!, <String, String>{'text': 'Update Hello World'});
}
The updated dynamic data for both ActivityKit updates and remote push notification updates can’t exceed 4KB in size. doc
For more solutions, please refer to live_activities
- End a Live Activity
if(_activityId != null) {
await _liveActivities.endActivity(_activityId!);
}
- End all Live Activities
await _liveActivities.endAllActivities();
- Get all Live Activities id
await _liveActivities.getAllActivities()
5. Deeplink
- The default urlScheme is
fla
FlutterLiveActivities({this.urlScheme = 'fla'})
- Add urlScheme in your project
- Swift code:
@available(iOSApplicationExtension 16.1, *)
struct live_activity_testLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: FlutterLiveActivities.self) { context in
let data = TestData(JSONData: context.state.data)
// Lock screen/banner UI goes here
VStack(alignment: .leading) {
Text(data?.text ?? "")
HStack {
// Create an action via `Link`
Link(destination: URL(string: "fla://xx.xx/tap/A")!) {
Text("A")
.frame(width: 40, height: 40)
.background(.blue)
}
// Create an action via `Link`
Link(destination: URL(string: "fla://xx.xx/tap/B")!) {
Text("B")
.frame(width: 40, height: 40)
.background(.blue)
}
// Create an action via `Link`
Link(destination: URL(string: "fla://xx.xx/tap/C")!) {
Text("C")
.frame(width: 40, height: 40)
.background(.blue)
}
}
.frame(width: .infinity, height: .infinity)
}
.padding(20)
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in
let data = TestData(JSONData: context.state.data)
return DynamicIsland {
DynamicIslandExpandedRegion(.bottom) {
// Create an action via `Link`
Link(destination: URL(string: "fla://xxxxxxx.xxxxxx")!) {
Text(data?.text ?? "")
.background(.red)
}
}
} compactLeading: {
Text("L")
} compactTrailing: {
Text("T")
} minimal: {
Text("Min")
}
.widgetURL(URL(string: "fla://www.apple.com")) // or use widgetURL
.keylineTint(Color.red)
}
}
}
- Dart code:
_subscription ??= _liveActivities.uriStream().listen((String? uri) {
dev.log('deeplink uri: $uri');
});
6. Display image
Due to block size limitations. We can't send metadata to LiveActivities
LiveActivities does not support async loading, so we can't use AsyncImage or read local file
Solution from Developer Forums: 716902
- Add group config (Paid account required)
- Add group id both Runner and Widget
- Send image to group:
Dart code:
Future<void> _sendImageToGroup() async {
const String url = 'https://cdn.iconscout.com/icon/free/png-256/flutter-2752187-2285004.png';
final String? path = await ImageHelper.getFilePathFromUrl(url);
if (path != null) {
_liveActivities.sendImageToGroup(
id: 'test-img',
filePath: path,
groupId: 'group.live_example',
);
}
}
Swift code:
DynamicIslandExpandedRegion(.leading) {
if let imageContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.live_example")?.appendingPathComponent("test-img"), /// Use id here
let uiImage = UIImage(contentsOfFile: imageContainer.path())
{
Image(uiImage: uiImage)
.resizable()
.frame(width: 53, height: 53)
.cornerRadius(13)
} else {
Text("Leading")
}
}