flutter_live_activities

repository·main·Indexed 43 days ago

https://github.com/istornz/flutter_live_activities

A Flutter plugin for implementing real-time, dynamic UI updates on iOS and Android. It leverages ActivityKit for iOS (16.1+) to support Live Activities on the Lock Screen and Dynamic Island, and the RemoteViews API for Android (API 24+). Features include support for passing assets, handling URL schemes for app communication, relevanceScore for activity prioritization on iOS 16.2+, and remote updates via push notifications.

Tokens
10.8K
Snippets
33
Records
39
Agent score
54%

What's inside flutter_live_activities

  1. Overview of live_activities

    main

    The live_activities plugin allows Flutter developers to implement real-time, dynamic UI elements on mobile devices. It leverages native APIs to provide high-visibility updates outside of the main application interface.

    Supported Features

    • iOS (16.1+): Uses the ActivityKit API to support Live Activities on the Lock Screen and the Dynamic Island on compatible iPhones.
    • Android (API 24+): Uses the RemoteViews API to create remote views for live updates.

    Platform Requirements

    • iOS: Requires version 16.1 or higher. On older versions, the plugin will silently do nothing.
    • Android: Requires API level 24 or higher. On older versions, the plugin will silently do nothing.
  2. Access Flutter Data in iOS Swift Code

    main

    To access data passed from Flutter within your Swift widget, use UserDefaults with the App Group ID you configured. Use a helper method to access keys prefixed with the activity ID to ensure uniqueness.

    // 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
          // Access Flutter properties using the prefixed key
          let myVariableFromFlutter = sharedDefault.string(forKey: context.attributes.prefixedKey("myVariableFromFlutter"))!
    
          // [...]
        }
      }
    }
  3. Setup iOS Live Activities and Dynamic Island

    main

    Because of technical restrictions, iOS Live Activities require a native Widget Extension implemented in Swift or Objective-C. Follow these steps to configure your project:

    1. Create a Widget Extension:

      • Open ios/Runner.xcworkspace in Xcode.
      • Go to File -> New -> Target....
      • Select Widget Extension and click Next.
      • Provide a product name (e.g., MyAppWidget) and ensure "Runner" is selected in the "Embed in Application" dropdown.
      • Click Finish and then Activate on the resulting alert.
    2. Configure Capabilities:

      • Add Push Notifications capability to the main Runner app only.
      • Add the App Group capability to both the Runner app and your Widget Extension. Note the identifier (e.g., group.example.myapp); this is your YOUR_GROUP_ID.
    3. Enable Live Activities in Info.plist: Add the following key to the Info.plist of both the Runner target and the Widget Extension target:

      <key>NSSupportsLiveActivities</key>
      <true/>
    <key>NSSupportsLiveActivities</key>
    <true/>
  4. Setup Android Live Activities

    main

    Android implementation requires a custom LiveActivityManager and a RemoteView XML layout.

    1. Register Manager in MainActivity.kt: In your MainActivity.kt, assign an instance of your custom manager to LiveActivityManagerHolder.instance inside configureFlutterEngine.

    2. Create CustomLiveActivityManager.kt: Extend LiveActivityManager and implement the buildNotification method. This method is called by the plugin to construct the notification using the provided Notification.Builder and the data map passed from Flutter.

    3. Create RemoteView Layout: Create an XML file at android/app/src/main/res/layout/live_activity.xml to define the visual design of the notification.

    // MainActivity.kt example
    class MainActivity : FlutterActivity() {
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            super.configureFlutterEngine(flutterEngine)
            LiveActivityManagerHolder.instance = CustomLiveActivityManager(this)
        }
    }
  5. Update Live Activities via Push Notifications

    main

    When your app is in the background or killed, you cannot use updateActivity() from the Flutter side. Instead, you must use Push Notifications from your server.

    Steps:

    1. Get the Push Token: Listen to activityUpdateStream and extract the activityToken from the event.
    2. Server Update: Send a push notification to that token. To target a specific notification for matchName, use the format {notification_id}_{matchName} (e.g., 35253464632_matchName).
    // Recommended: Listen to the stream to get the token
    _liveActivitiesPlugin.activityUpdateStream.listen((event) {
      event.map(
        active: (activity) {
          print(activity.activityToken); // Use this token on your server
        },
        ended: (activity) {},
        unknown: (activity) {},
      );
    });
  6. Use Push-to-Start Live Activities (iOS 17.2+)

    main

    iOS 17.2 allows creating Live Activities remotely via push notifications before the user opens the app.

    Recommended Workflow: Instead of managing everything from the server, send a standard notification to the user. When they open the app, launch the Live Activity and send the resulting activityID and PushToken to your server.

    Implementation:

    1. Check support using allowsPushStart().
    2. Listen to pushToStartTokenUpdateStream to receive tokens to send to your server.
    3. You can disable remote updates for specific activities by setting iOSEnableRemoteUpdates: false in createActivity or createOrUpdateActivity.
    // 1. Check support
    final isPushToStartSupported = await _liveActivitiesPlugin.allowsPushStart();
    
    if (isPushToStartSupported) {
      // 2. Listen for tokens to send to your server
      _liveActivitiesPlugin.pushToStartTokenUpdateStream.listen((String token) {
        print('Received push-to-start token: $token');
      });
    }
  7. Implement iOS Live Activity Attributes in Swift

    main

    In your Swift extension file (ExtensionNameLiveActivity.swift), you must define an ActivityAttributes struct named exactly LiveActivitiesAppAttributes. If this name is changed, the activity will be created but will not appear on the device.

    You must also include a typealias LiveDeliveryData = ContentState line for the data to display correctly.

    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()
    }
    
    // Create a Swift extension to handle prefixed keys
    extension LiveActivitiesAppAttributes {
      func prefixedKey(_ key: String) -> String {
        return "\(id)_\(key)"
      }
    }
  8. Access Flutter data from Swift (iOS)

    main

    To access data passed from Flutter in your Swift Live Activity extension, you must use UserDefaults with the same suiteName (App Group ID) used in your Flutter app. Use the prefixedKey method on the activity context to retrieve values using the keys defined in your Dart map.

    Important: Ensure the App Group ID is identical in both the Swift extension and the Flutter app configuration.

    // 1. Create the shared instance using your App Group ID
    let sharedDefault = UserDefaults(suiteName: "YOUR_GROUP_ID")!
    
    // 2. Access typed data using prefixed keys
    let pizzaName = sharedDefault.string(forKey: context.attributes.prefixedKey("name"))!
    let pizzaPrice = sharedDefault.float(forKey: context.attributes.prefixedKey("price"))
    let quantity = sharedDefault.integer(forKey: context.attributes.prefixedKey("quantity"))
  9. Communicate between Native and Flutter via URL Schemes

    main

    To allow a user to tap a link in a Live Activity and open your Flutter app, you must set up a custom URL scheme.

    1. Xcode Setup: Navigate to Runner > Runner > URL Types and add a new URL Scheme (e.g., str or la).
    2. Swift Implementation: Use a Link view with the custom scheme.
    3. Flutter Initialization: Initialize the plugin with your custom scheme.
    4. Flutter Listening: Listen to the urlSchemeStream() to handle the incoming data.
    // Swift: Create a link to your custom scheme
    Link(destination: URL(string: "la://my.app/order?=123")!) {
      Text("See order")
    }
    // Flutter: Initialize and listen
    _liveActivitiesPlugin.init(
      appGroupId: 'your.group.id',
      urlScheme: 'str', // The scheme you defined in Xcode
      requestAndroidNotificationPermission: true,
    );
    
    _liveActivitiesPlugin.urlSchemeStream().listen((schemeData) {
      // Handle the incoming URL data
    });
  10. Pass files (images/text) from Flutter to Native

    main

    You can pass files like images or text files from Flutter to your Live Activity by including specific file objects in your activityModel map.

    Available File Types:

    • LiveActivityFileFromAsset: Loads a file from Flutter assets.
    • LiveActivityFileFromUrl: Loads a file from an external URL.
    • LiveActivityFileFromMemory: Loads a file from a Uint8List object.

    Tip: For images, use the .image() factory to apply a resizeFactor via LiveActivityImageFileOptions. This is highly recommended to keep image resolutions small for the Dynamic Island/Lock Screen.

    Swift Usage:

    • For images: Use UIImage(contentsOfFile: path) where the path is retrieved from UserDefaults via a prefixed key.
    • For text: Use String(contentsOfFile: path, encoding: .utf8).
    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);