flutter_activity_kit

pub package License: MIT iOS: 16.1+ Android: 7.0+

Unified Flutter API for iOS Live Activities (Dynamic Island & Lock Screen) and Android Ongoing Notifications with push token sync, rich state models, and a pure Dart UI DSL.


🌟 What's New in v0.5.1

  • 🎨 Pure Dart UI DSL: Build custom Live Activity widgets in 100% Dart (LAColumn, LARow, LAText, LAImage, LAProgressBar, LAButton, LASpacer, LAContainer, LATimer) and transpile them to native SwiftUI.
  • πŸ“ Smart Folder Scanning (lib/live_activity_widgets/): Put your Dart widget definitions in lib/live_activity_widgets/ and run dart run flutter_activity_kit:generate_swift to generate all Swift widgets automatically.
  • 🧩 Built-in Templates: Pre-configured templates for navigation (Mini-Map route canvas), delivery, sports (live scoreboard), workout (chronometer & metrics), and generic.
  • πŸ—ΊοΈ Mini-Map & Route Tracking: Native vector route canvas for Dynamic Island and Lock Screen.
  • πŸ“ Compact Lock Screen Banner: Optimized padding and sizing to eliminate clipping within Apple's 160pt height constraint.

Features

  • iOS 16.1+ Live Activities: Lock Screen banners, Dynamic Island (compact, expanded, minimal).
  • Android Ongoing Notifications: High priority status bar chips, progress bars, timers, and interactive action buttons.
  • Hardware Timers: 60 FPS real-time countdowns and chronometers rendered natively by the OS.
  • Pure Dart UI DSL: Write Live Activity layouts in Dart; transpile to native SwiftUI.
  • APNs & FCM Remote Sync: Push-to-update and iOS 17.2+ push-to-start token streams.
  • Reactive Controller: ActivityController<T> with auto-sync and built-in debouncing.
  • In-App Previews: Flutter preview widgets for Dynamic Island and Android notifications.

Install

dependencies:
  flutter_activity_kit: ^0.5.1

🎨 Building Live Activities in Pure Dart

You can build Live Activities in 100% pure Dart without writing any Swift!

1. Define your widget in Dart:

Create lib/live_activity_widgets/flight_live_activity_widget.dart:

import 'package:flutter_activity_kit/flutter_activity_kit.dart';

class FlightLiveActivityWidget extends LiveActivityWidgetDefinition {
  const FlightLiveActivityWidget()
      : super(
          name: 'FlightTracker',
          activityType: 'FlightAttributes',
          actions: const [
            ActivityAction(id: 'boarding_pass', title: 'Boarding Pass'),
          ],
        );

  @override
  LAWidget buildLockScreen(LAContext context) {
    return LAColumn(
      spacing: 6,
      children: [
        LARow(
          children: [
            const LAImage.system('airplane.departure', color: LAColor.cyan),
            LAText(context.title, font: LAFont.headline, bold: true),
            const LASpacer(),
            LAText(context.status, font: LAFont.caption, bold: true, color: LAColor.cyan),
          ],
        ),
        LAText(context.message, font: LAFont.subheadline, color: LAColor.gray),
        const LAProgressBar(tint: LAColor.cyan),
        const LARow(
          children: [
            LAButton(
              title: 'Boarding Pass',
              actionId: 'boarding_pass',
              systemIcon: 'ticket.fill',
              isProminent: true,
              tint: LAColor.cyan,
            ),
          ],
        ),
      ],
    );
  }
}

2. Transpile to Swift:

Run the generator in your project root:

dart run flutter_activity_kit:generate_swift

The generator will scan lib/live_activity_widgets/ and generate native, crash-proof SwiftUI code in ios/LiveActivityWidget/.


πŸ“¦ Built-In Templates

If you don't want to design custom layouts, choose from pre-configured templates:

Template CLI Command Features
navigation dart run flutter_activity_kit:generate_swift --name Ride --template navigation Vector Route Mini-Map, ETA badge, Waypoints, Call/Share actions
delivery dart run flutter_activity_kit:generate_swift --name Order --template delivery Step progress bar, Courier status, Call Courier / Cancel
sports dart run flutter_activity_kit:generate_swift --name Match --template sports Live Match Scoreboard, Team shields, Match Stats action
workout dart run flutter_activity_kit:generate_swift --name Run --template workout Real-time Chronometer, Pace & Distance metrics, Pause/Finish
generic dart run flutter_activity_kit:generate_swift --name Generic --template generic Multi-purpose status capsule, progress bar, timer, and buttons

πŸš€ Quick Start in Dart

Start an Activity (1 line):

import 'package:flutter_activity_kit/flutter_activity_kit.dart';

final session = await FlutterActivityKit.start(
  activityType: 'DeliveryAttributes',
  title: 'Bella Pizza',
  message: 'Chef is baking your pizza',
  status: 'Baking πŸ”₯',
  progress: 0.45,
  countdown: const Duration(minutes: 18), // Hardware-rendered 60 FPS countdown!
  attributes: const {'orderId': 'ORD-9812'},
  actions: const [
    ActivityAction(id: 'call_driver', title: 'Call Driver', icon: 'ic_menu_call'),
    ActivityAction(id: 'cancel_order', title: 'Cancel', isDestructive: true),
  ],
);

Update the Activity:

await session.quickUpdate(
  title: 'Out for Delivery',
  message: 'Driver Alex is on the way (0.8 miles away)',
  status: 'On the Way πŸ›΅',
  progress: 0.85,
  countdown: const Duration(minutes: 5),
);

End the Activity:

await session.quickEnd(
  title: 'Order Delivered',
  message: 'Enjoy your meal!',
  status: 'Delivered πŸŽ‰',
  dismissalPolicy: ActivityDismissalPolicy.immediate,
);

πŸŽ›οΈ Handling Action Button Taps

When the user taps an action button on the iOS Lock Screen banner or Android Notification:

// Wrap your root widget or listen declaratively:
FlutterActivityKit.onAction('call_driver', (event) async {
  print('User tapped Call Driver for activity: ${event.activityId}');
  final phoneUrl = Uri.parse('tel:+15550199');
  if (await canLaunchUrl(phoneUrl)) {
    await launchUrl(phoneUrl);
  }
});

⚑ Reactive Controller (ActivityController<T>)

For state-driven apps (workouts, real-time tracking, WebSocket streams):

final workoutController = ActivityController<WorkoutState>(
  initialState: WorkoutState(distance: 0.0, pace: '0:00'),
  activityType: 'WorkoutAttributes',
  stateToContent: (state) => ActivityContent(
    state: MapActivityContentState({
      'title': 'Outdoor Run',
      'message': 'Distance: ${state.distance} km β€’ Pace: ${state.pace}',
      'progress': state.distance / 10.0,
    }),
  ),
);

// Start
await workoutController.start();

// Update anywhere in your business logic (auto-debounced to prevent OS rate limits):
workoutController.updateState(
  WorkoutState(distance: 4.2, pace: '5:10 min/km'),
);

πŸ“‘ Remote Push Sync (APNs & FCM)

Listen for device tokens to push updates directly from your backend server:

FlutterActivityKit.pushTokenEvents.listen((event) {
  final activityId = event.activityId;
  final apnsToken = event.pushToken;

  // Send apnsToken to your backend (Node.js, Go, Firebase)
  apiService.registerPushToken(activityId: activityId, token: apnsToken);
});

βš™οΈ Platform Setup

iOS

  1. In ios/Runner/Info.plist add:
    <key>NSSupportsLiveActivities</key>
    <true/>
    <key>NSSupportsLiveActivitiesFrequentUpdates</key>
    <true/>
    
  2. In Xcode: File βž” New βž” Target βž” Widget Extension (name it LiveActivityWidget, check Include Live Activity).
  3. Run dart run flutter_activity_kit:generate_swift and add the generated file to your Widget Extension target.

Android

No native configuration needed. Permissions and ongoing notification channels are handled automatically.


πŸ“„ License

MIT License. Created by PinzaruLab.