Skip to content
Nadir Ben SalahCVGet in touch

Live Activities in React Native: what the tutorials leave out

Every tutorial stops at the hello world in the Dynamic Island. This is what happens after that, learned from shipping two of them.

· 8 min read · Nadir Ben Salah

Getting a Live Activity on screen is an afternoon's work. There are enough tutorials for that, and they are broadly correct. What none of them prepare you for is the month afterwards, when the thing is in front of real users on real devices.

I have shipped two: a parking countdown in Ticket, which is fully native, and prayer times in Qindil, which is React Native. Everything below applies to both, because the Live Activity itself is Swift either way. These are the things that cost me time.

It is a widget extension, not part of your app

The Live Activity runs in a separate process, written in SwiftUI, with no access to your React Native code. Whatever it displays has to be handed to it, either at start time or through an update.

The practical consequence is that your ActivityAttributes becomes an API contract between two processes. Get it wrong and you are shipping an app update to fix a layout. Design it as if you cannot change it, because changing it is expensive.

Three layouts, and the one everybody forgets

The Dynamic Island has a compact form, a minimal form and an expanded form. The Lock Screen has its own. That is four states, and the minimal one is the trap: it appears when a second activity is running, and it is barely wider than a circle.

If your minimal layout assumes any width at all, it will be clipped the first time a user has a timer running alongside your app. Design the minimal state first, not last.

Update budgets are real and mostly undocumented

You cannot update a Live Activity as often as you like. iOS enforces a budget, and when you exceed it your updates are simply dropped. No error, no callback, nothing in the logs.

For a countdown, the answer is not to update at all. Pass the end date once and let SwiftUI render the countdown natively with a Text timer. Ticket runs a full parking session with essentially no updates after the initial start. It is the cheapest and most reliable approach, and it is what makes a 3.3 MB app feel instant.

Update only when the underlying fact changes: the user adds fifteen minutes, the train is delayed, the score changes. Not on a timer.

Ending the activity is harder than starting it

The case everybody misses: the user force-quits your app while the activity is live. Your JavaScript is gone, your app is not running, and the activity is still on their Lock Screen.

If you have nothing in place, it sits there until iOS eventually clears it, which can be hours. Set a dismissal policy when you start the activity, and give it a sensible end date even when you expect to end it manually. Treat the manual end as the happy path, not the only path.

Buttons on the Lock Screen need App Intents

A Live Activity with interactive buttons is where this gets genuinely useful — "add 15 minutes" without unlocking the phone. Those buttons are App Intents, which run in yet another process and cannot touch your JavaScript either.

Which means the same shared-state problem as everywhere else in this stack. I wrote about the two honest strategies in the App Intents piece, and they apply identically here.

What I would tell you before you start

  • Design the minimal Dynamic Island state first. It is the one that breaks.
  • Pass an end date and let SwiftUI count down. Do not push updates every second.
  • Handle the force-quit case explicitly, from day one.
  • Keep the shared attributes small. Every field is a field that can drift between two processes.
  • Test with a second Live Activity running. Most bugs in mine only appeared in that state.

Stuck on this in your own app?

This is the kind of work I take on: the native layer a React Native team cannot reach.

What I can addGet in touch

Read next