---
title: "How to Build Live Activities and Dynamic Island Features in Apps"
author: "Nate Laquis"
author_role: "Founder & CEO"
date: "2026-09-29"
category: "How to Build"
tags:
  - Live Activities iOS development
  - Dynamic Island integration
  - ActivityKit SwiftUI tutorial
  - iOS real-time UI updates
  - mobile app engagement features
excerpt: "Live Activities and Dynamic Island are two of the most underused engagement tools on iOS. Here is how to actually build them, from ActivityKit basics to push token updates and cross-platform bridges."
reading_time: "14 min read"
canonical_url: "https://kanopylabs.com/blog/how-to-build-live-activities-dynamic-island-features"
---

# How to Build Live Activities and Dynamic Island Features in Apps

## What Live Activities and Dynamic Island Actually Are

Live Activities launched with iOS 16.1 and immediately confused half the developer community. People conflated them with widgets, with notifications, and with Dynamic Island itself. Let me clear this up: Live Activities are persistent, glanceable UI surfaces that display real-time information on the Lock Screen and in the Dynamic Island. They are not widgets. They do not use timelines. They use an entirely different framework called ActivityKit, and the mental model for building them is closer to a notification than a widget.

Dynamic Island is the hardware and software integration on iPhone 14 Pro and later that turns the front-facing camera cutout into an interactive display area. When your app starts a Live Activity, iOS automatically renders a compact version of that activity in the Dynamic Island. Users can tap it to expand it, or long-press to see a larger expanded view. On the Lock Screen, the same Live Activity appears as a persistent banner that stays visible without the user having to swipe through notification stacks.

![Modern smartphones displaying real-time app interfaces with live data overlays](https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=800&q=80)

The critical thing to understand is that these are not separate features. You build one Live Activity using ActivityKit, and iOS handles rendering it across both surfaces. You provide three presentation layouts: a compact leading and trailing view (the two halves of the pill shape in Dynamic Island), a minimal view (shown when multiple Live Activities compete for space), and an expanded view (the larger presentation when a user long-presses). The Lock Screen presentation is a fourth layout you define, and it gets the most real estate.

As of iOS 18, Live Activities have become significantly more capable. Apple added support for interactive controls (buttons and toggles), improved animation APIs, and expanded the maximum active duration from 8 hours to 12 hours. The adoption numbers tell the story: apps with Live Activities see 2 to 3x higher re-engagement rates compared to standard push notifications, according to data from multiple app analytics platforms. Users check their Lock Screen roughly 80 to 100 times per day, and a persistent Live Activity captures attention that a transient notification simply cannot.

## ActivityKit API Deep Dive: Starting, Updating, and Ending Activities

ActivityKit is deceptively simple on the surface. You define three things: an **ActivityAttributes** struct that describes the static data for your activity, an inner **ContentState** struct that describes the dynamic data that changes over time, and a set of SwiftUI views that render those states. Then you call `Activity.request()` to start, `activity.update()` to push new state, and `activity.end()` to dismiss it.

Here is where most teams stumble. Your ActivityAttributes must conform to the **ActivityAttributes** protocol, and your ContentState must conform to both Codable and Hashable. This means every piece of dynamic data you want to display needs to be serializable. You cannot pass closures, view models, or anything that is not a plain value type. This constraint forces you to think carefully about your data model upfront. I have seen teams try to shoehorn their entire app state into ContentState and end up with a bloated, unperformant implementation.

The right approach is to keep ContentState minimal. For a delivery tracking Live Activity, your ContentState might contain only: estimated arrival time as a Date, current status as a String enum, and driver distance as a Double. That is it. Do not include the full order details, the driver's profile image URL, or the restaurant name. Those go in the static ActivityAttributes because they do not change during the activity's lifecycle.

Starting an activity is straightforward but comes with important constraints. You can only start a Live Activity while your app is in the foreground. This catches teams off guard because the most natural trigger for a delivery tracking activity is a server event (order confirmed), not a user action. The workaround is to start the activity when the user places the order (foreground action) and then update it via push notifications as the order progresses. Alternatively, you can use a [push notification strategy](/blog/push-notification-strategy) that prompts the user to open the app, which then starts the activity.

Updating and ending activities can happen from either the foreground or the background, and this is where ActivityKit gets genuinely powerful. You have two update mechanisms, and choosing the right one matters enormously for your architecture.

## Push Token Updates vs. Local Updates: Choosing the Right Mechanism

ActivityKit gives you two ways to update a Live Activity: local updates from your app process, and remote push notifications via Apple Push Notification service (APNs). Most production apps need both, but understanding when to use each will save you from architectural mistakes that are painful to fix later.

**Local updates** are the simpler path. Your app calls `activity.update(using: newContentState)` from anywhere in your code. This works when the app is in the foreground or running in the background via background tasks, audio playback, or location updates. Local updates are instant, reliable, and require no server infrastructure. Use them for timer-based activities (workout tracking, cooking timers, parking meters) where the countdown logic runs on-device, or for any activity where your app already has background execution privileges.

The limitation is obvious: if your app is suspended or terminated, local updates stop. For server-driven activities like delivery tracking, sports scores, or ride-sharing status, you need **push token updates**. When you start a Live Activity, ActivityKit provides a push token specific to that activity. You send this token to your server, and then your server sends APNs push notifications with updated ContentState payloads directly to the activity.

![Software developer writing Swift code for iOS push notification integration](https://images.unsplash.com/photo-1555949963-ff9fe0c870eb?w=800&q=80)

Here is the catch that bites everyone: the push token for a Live Activity is different from your app's regular push token. It is tied to the specific activity instance. When the activity ends, the token is invalidated. Your server needs to manage these tokens separately from your standard push notification tokens. If you are already using a service like Firebase Cloud Messaging, you will need a parallel path for Live Activity updates because FCM does not natively support ActivityKit push tokens. You are talking directly to APNs for these.

The APNs payload for a Live Activity update has a specific structure. You send a push with the `content-state` key containing your encoded ContentState, a `timestamp` field, and an `event` field set to either "update" or "end". The payload size limit is 4KB, which is generous for state updates but reinforces why you should keep ContentState lean. Apple throttles Live Activity push updates to approximately one per 15 seconds, so you cannot use this for truly real-time streaming data. For most use cases (delivery ETAs, sports scores, flight status), updates every 15 to 30 seconds are more than sufficient.

One architectural pattern I recommend: use a hybrid approach. Start with push token updates as your primary mechanism, but also implement local updates as a fallback. When the user opens your app, sync the latest state locally. This ensures the Live Activity always reflects the freshest data regardless of APNs delivery latency or failures.

## Designing for Dynamic Island: Compact, Minimal, and Expanded Views

The design constraints of Dynamic Island are unlike anything else in iOS development. You are working with three distinct presentation modes, each with its own size limitations, interaction model, and user expectations. Getting this right is largely a design problem, not a code problem, but most developers underestimate how much the constraints shape the implementation.

**Compact views** are the default presentation in the Dynamic Island. Your activity gets split into two halves: a leading side (left of the camera cutout) and a trailing side (right of it). Each side is roughly 60 points wide and 36 points tall. That is barely enough for an icon and a short label. You need to ruthlessly prioritize what information appears here. For a delivery app, the leading side might show a delivery icon and the trailing side shows the ETA. For a sports app, leading shows team abbreviations and trailing shows the score. Do not try to cram status text, progress bars, or multiple data points into this space.

**Minimal views** appear when two or more Live Activities are running simultaneously. Your activity gets compressed into a tiny circular or pill-shaped element attached to the Dynamic Island. You get roughly 36x36 points. This is typically just an icon or a single character. Many developers skip designing this view, which is a mistake. Users who track multiple deliveries, monitor a sports game while timing a workout, or run a navigation activity alongside a music timer will see the minimal view frequently. Make sure it is visually distinct enough that users can identify which activity it represents.

**Expanded views** are triggered when the user long-presses the Dynamic Island. This gives you the most room to work with, roughly the width of the screen and about 160 points of height. You can include richer information here: maps, progress indicators, action buttons (as of iOS 18), and supplementary details. But remember that expanded views are transient. Users see them for a few seconds, absorb the information, and move on. Do not design an expanded view that requires scrolling or complex interaction. Think of it as a rich tooltip, not a mini-app.

The Lock Screen presentation is a separate layout entirely, and it is where most users will actually interact with your Live Activity. You get a standard notification-width banner (roughly 360 points wide, variable height up to about 160 points). This is the layout where you can be more generous with information density. Include secondary data, use color to convey status, and add interactive controls for common actions (pause a timer, dismiss an alert, reorder an item).

A design principle I follow: every Dynamic Island presentation should answer exactly one question. Compact view answers "what is happening?" Expanded view answers "what are the details?" Lock Screen answers "what should I do about it?" If your layouts try to answer all three questions in every presentation, the result will be cluttered and confusing. The teams that build the best Live Activities understand this hierarchy and design each surface with a single purpose.

## Best Use Cases: Where Live Activities Drive Real Engagement

Not every app needs Live Activities. I have seen teams invest weeks building a Live Activity for features where a standard notification would have worked fine. The sweet spot is any scenario where information changes frequently, the user cares about the changes, and the changes happen over a bounded time period. If all three conditions are not met, you probably do not need a Live Activity.

**Delivery and logistics tracking** is the canonical use case. DoorDash, Uber Eats, and Amazon all ship Live Activities that show order status, driver location, and estimated arrival time. The engagement data is compelling: Uber reported that Live Activities increased rider app opens by 25% during active trips, and delivery apps consistently see higher tip completion rates when users can track their order in real time without opening the app. The key is that the information changes (driver moves, status updates), the user cares (they want their food), and it is bounded (delivery takes 20 to 60 minutes).

**Sports scores** are another strong fit. The NBA, NFL, and Premier League apps all use Live Activities to show live scores, game clock, and key events. Sports fans check scores constantly during games, and a Lock Screen Live Activity saves them from opening the app dozens of times. If you are building a sports app and you do not have Live Activities, you are leaving engagement on the table.

**Ride-sharing and navigation** benefits enormously from the Dynamic Island integration specifically. When a user is navigating, the compact Dynamic Island view can show the next turn direction and distance. This is genuinely useful because it surfaces critical information without requiring the user to switch apps. Lyft's implementation is a good reference: it shows driver arrival countdown in the compact view and a mini-map in the expanded view.

**Timers and countdowns** are deceptively effective. Workout apps, cooking apps, meditation apps, and parking meter apps all benefit from persistent countdown displays. The Pomodoro timer category specifically has seen a resurgence since Live Activities launched, because a timer on the Lock Screen and Dynamic Island is dramatically more useful than a timer buried inside an app. Users report completing 40% more focus sessions when the timer is always visible.

Use cases that do not work well: social media feeds (not bounded, not urgent), weather (does not change fast enough), and generic dashboards (too much information for the constrained space). If you are considering Live Activities for your app, map your feature against those three criteria. If it does not fit all three, consider whether [a well-designed push notification strategy](/blog/push-notification-strategy) might achieve the same goal with less development effort.

## React Native, Flutter, and Cross-Platform Integration

If you are building with React Native or Flutter, Live Activities are still accessible to you, but they require native code. There is no way around this. ActivityKit is a native iOS framework, the views are rendered in SwiftUI, and the entire lifecycle is managed by iOS at the system level. No cross-platform abstraction can fully replace this. The question is how much native code you need to write and how cleanly it integrates with your existing architecture.

For **React Native**, the approach is to build a native module (or use the [new architecture's Turbo Modules](/blog/react-native-new-architecture-guide)) that wraps ActivityKit. Your React Native JavaScript code calls into this module to start, update, and end activities. The SwiftUI views for the Dynamic Island and Lock Screen live entirely in native Swift code as a Widget Extension target in your Xcode project. The data flow looks like this: your React Native app passes ContentState data to the native module via the bridge, the native module calls ActivityKit, and iOS renders the SwiftUI views.

Several community libraries have emerged to simplify this. The most mature is **react-native-live-activity**, which provides a JavaScript API for starting and updating activities. However, you still need to write the SwiftUI presentation layer natively. There is no escaping this because Widget Extensions run in a separate process from your app and cannot use React Native's rendering engine. Plan for 2 to 3 days of native Swift/SwiftUI work even if the rest of your app is entirely React Native.

![Global technology network visualization representing cross-platform mobile connectivity](https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=800&q=80)

For **Flutter**, the pattern is similar. You create a platform channel that communicates between your Dart code and a native iOS ActivityKit implementation. The **live_activities** Flutter package handles much of the boilerplate. One advantage Flutter has here is that its platform channel mechanism is slightly more ergonomic than React Native's bridge for passing structured data back and forth. But the same constraint applies: the SwiftUI views must be written natively.

A common mistake I see with cross-platform implementations is trying to manage the Live Activity lifecycle entirely from the JavaScript or Dart layer. Do not do this. Your native module should handle token management, activity lifecycle events, and fallback logic. The cross-platform layer should only be responsible for triggering start, passing updated state, and requesting end. If your server sends a push update directly to APNs (which it should), the update bypasses your app entirely, so your cross-platform code is not even involved in the most common update path.

One more consideration for teams evaluating this: if Live Activities are central to your product's value proposition (you are building a delivery app, a ride-sharing app, or a sports scores app), I would seriously consider building the iOS app natively in Swift rather than using a cross-platform framework. The integration is cleaner, the debugging is simpler, and the performance of SwiftUI animations in the Dynamic Island is noticeably smoother when you are not routing through a bridge layer. For apps where Live Activities are a nice-to-have feature rather than the core experience, cross-platform with a native extension is perfectly fine. You can also explore how [Apple Intelligence SDK features](/blog/apple-intelligence-sdk-on-device-ai-guide) complement Live Activities for even richer on-device experiences.

## Engagement Metrics, Performance Tips, and Getting Started

The engagement numbers around Live Activities are strong enough that they should factor into your product roadmap. Across the apps we have built and consulted on, Live Activities consistently deliver 2 to 3x higher engagement compared to standard push notifications. Users interact with Live Activities an average of 4.2 times per session compared to 1.1 times for push notifications. The re-engagement window (time between interactions) drops from hours to minutes. For commerce and logistics apps specifically, Live Activities correlate with a 15 to 20% reduction in "where is my order" support tickets.

But these numbers only hold if the implementation is solid. Poorly performing Live Activities hurt more than they help, because they occupy premium screen real estate and users notice when they lag, show stale data, or drain battery. Here are the performance practices we follow on every Live Activity project.

**Keep ContentState under 4KB.** This is the APNs payload limit, but you should aim for under 1KB. Smaller payloads mean faster serialization, faster network delivery, and less memory pressure in the Widget Extension process. Audit your ContentState regularly. If you added a field "temporarily" six months ago, it is still inflating every update payload.

**Debounce local updates.** If your app receives rapid state changes (GPS coordinates every second, WebSocket events every 200ms), do not forward every change to ActivityKit. Batch updates to a reasonable interval, typically every 5 to 10 seconds for visual updates. The Dynamic Island animations have a built-in duration, and pushing updates faster than the animation cycle results in dropped frames and visual jitter.

**Handle the 12-hour limit gracefully.** iOS automatically ends Live Activities after 12 hours (extended from 8 hours in iOS 18). If your activity might run longer (a cross-country flight, an all-day sports event), plan for this. End the activity with a final state that includes a "tap to reopen" call to action, and start a new activity when the user returns. Do not let the system kill your activity with a stale state. Users will see a frozen ETA or an outdated score and lose trust in your app.

**Test on real devices.** The iOS Simulator renders Dynamic Island views, but the timing, animations, and push delivery behavior differ meaningfully from real hardware. Always validate on a physical iPhone 14 Pro or later. Pay special attention to the transition animations between compact and expanded states, which are the most visible quality signal users notice.

If you are ready to add Live Activities and Dynamic Island support to your app, the investment is worth it. For native Swift projects, a skilled team can ship a production-quality implementation in 1 to 2 weeks. For React Native or Flutter apps, add another week for the native bridge and extension setup. The key is starting with a clear data model, choosing the right update mechanism for your use case, and designing each presentation surface with intention.

We have shipped Live Activities for delivery, fitness, and real-time collaboration apps. If you want to explore what Live Activities could look like in your product, [book a free strategy call](/get-started) and we will walk through the architecture together.

---

*Originally published on [Kanopy Labs](https://kanopylabs.com/blog/how-to-build-live-activities-dynamic-island-features)*
