iOS

iOS

ActivityKit is Swift-only and renders in a widget extension, so two pieces live on the Swift side:

1. Swift package (app and widget extension)

Add swift/HaloKMP from the repository as a local package and link it from both targets. It provides:

  • HaloActivityAttributes — the single ActivityAttributes type ActivityKit uses to match an activity to its widget. Never copy or re-declare it; a duplicate type means the activity starts but nothing renders.
  • HaloKit and HaloKMPBridge — the ActivityKit calls, discovered by the Kotlin side at run time.
  • HaloActivityWidget — a default Lock Screen + Dynamic Island UI.

2. Widget extension

import HaloKMP
import SwiftUI
import WidgetKit
 
@main
struct MyWidgets: WidgetBundle {
    var body: some Widget {
        HaloActivityWidget()
    }
}

Add NSSupportsLiveActivities = YES to the app’s Info.plist. The extension’s Info.plist needs NSExtensionPointIdentifier = com.apple.widgetkit-extension. See sample/composeApp/iosApp/project.yml for a complete xcodegen definition.

For a custom look, write your own ActivityConfiguration(for: HaloActivityAttributes.self) and read context.state (title, subtitle, timerRange, progressFraction, values).

3. Nothing else

The Kotlin side finds the package’s HaloKMPBridge class at run time, so there is no bridge file to copy, no register call and no export of the Kotlin framework; implementation(...) in commonMain is enough. Apps that bring their own ActivityKit code can still pass a LiveActivityBridge to Halo.register, which then takes precedence.

Link the Kotlin framework exactly once; a second copy has its own empty bridge registry.

Behaviour

  • Floor is iOS 16.2. isSupported is false when the HaloKMP package is not linked into the app.
  • authorization() is Denied when ActivityAuthorizationInfo().areActivitiesEnabled is false.
  • The system ends activities after eight hours; they surface as Expired.
  • staleAtEpochMillis becomes ActivityContent.staleDate; the default widget dims stale content.
  • Payloads over about 4 KB are refused by ActivityKit; the library caps encoded requests at 3 KB.