RORK LABJP
MAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code appsMAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code apps
Articles/Dev Tools
Dev Tools/2026-06-14Advanced

Updating Live Activities Remotely: Putting Live Lock Screen Info on a Rork App

A practical design for updating Live Activities remotely through APNs so the Lock Screen and Dynamic Island stay current even when your app is closed. Covers push-to-start vs update tokens, the content-state payload, stale-date and the update budget, and bridging from Expo, with working code and the issues I hit in production.

Live Activities2ActivityKitAPNs2Expo68Rork Max147iOS77

Premium Article

When I decided to show wallpaper download progress on the Lock Screen, the first thing that tripped me up was a single question: while the app is closed, who actually updates this display? A Live Activity keeps running when your app is in the background, but if the app itself tries to drive the updates, it runs straight into background execution limits.

The answer was to build a path that updates the Live Activity directly from the server over APNs. Get this design wrong and a stale number stays glued to the Lock Screen forever. The approach is the same whether you ship an Expo app generated by Rork or a native Swift app generated by Rork Max. Here is what I learned, in the order it caused trouble.

There are two update paths

A Live Activity can be updated in two ways: locally and remotely. Local updates call Activity.update(...) during the short window your app is in the foreground. They are immediate, but they stop the moment the app sleeps.

Remote updates push to APNs, and the system rewrites the display when it receives them. Because they arrive even when the app is closed, anything that "moves forward outside the app" — progress, scores, a courier's position — needs this path.

Running several apps solo as an indie developer, I settled on shaping the initial display locally for the first few seconds, then sending every continuing update remotely. Mixing the two halfway makes it impossible to know which value is authoritative.

Two tokens: push-to-start and update

The first thing to grasp about remote updates is that there are two kinds of token. One is the push-to-start token, used to start a Live Activity that does not yet exist from the server side. The other is the update token, used to update one specific Activity that is already running.

The push-to-start token is a single token for the whole app, and you subscribe to it at launch. The update token is issued per Activity and arrives asynchronously right after you start one. Confuse the order and you end up able to start an Activity but never deliver a single update afterward.

import ActivityKit
 
@available(iOS 17.2, *)
func registerPushToStart() {
    Task {
        for await data in Activity<DownloadAttributes>.pushToStartTokenUpdates {
            let token = data.map { String(format: "%02x", $0) }.joined()
            // One per app. Register "this user can accept a fresh start" with the server
            await sendToServer(kind: "start", token: token, activityId: nil)
        }
    }
}
 
@available(iOS 16.1, *)
func observeUpdateToken(for activity: Activity<DownloadAttributes>) {
    Task {
        for await tokenData in activity.pushTokenUpdates {
            let token = tokenData.map { String(format: "%02x", $0) }.joined()
            // Issued per Activity, and it expires. Overwrite on the server every time
            await sendToServer(kind: "update", token: token, activityId: activity.id)
        }
    }
}

The key detail is that pushTokenUpdates is not a one-shot fetch but a stream that emits more than once. Tokens rotate, so if you save the first one and call it done, updates quietly stop a few hours later. Keep the subscription alive and overwrite the server each time one arrives.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Follow which of the two tokens (push-to-start vs update) to fetch in what order, and where to send each to your server, in working Swift and TypeScript
Take home an APNs payload design with stale-date, dismissal-date and relevance-score, plus a sense of the numbers that keep you inside the update budget
Understand the three production traps (a frozen Lock Screen, a broken Dynamic Island, an expired token) and the exact way around each
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-04-05
Rork Max × WidgetKit & Live Activities Complete Guide 2026 — Dynamic Island Implementation
A complete guide to implementing iOS Widgets, Lock Screen widgets, and Dynamic Island Live Activities with Rork Max. Covers WidgetKit fundamentals, Timeline update strategies, App Intents integration, and monetization.
Dev Tools2026-06-12
Adding a Home Screen Widget to a Rork App — Making WidgetKit Work Within Expo's Constraints
Rork generates Expo apps, and home screen widgets can't be written in React Native. Here's how to wire up WidgetKit with a config plugin and App Groups — including the parts that tripped me up.
Dev Tools2026-06-02
Fixing 'No bundle URL present' on a Release Build to a Real iOS Device
How to read the 'No bundle URL present' error that shows up when you build a Rork-exported app locally in Xcode. We separate the Metro-connection case from the missing-embedded-bundle case, and walk through generating an offline bundle by hand with working commands.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →