Skip to content

Auto-restart Live Activity when iOS sends .ended#659

Open
bjorkert wants to merge 1 commit into
devfrom
fix-la-ended-autorestart
Open

Auto-restart Live Activity when iOS sends .ended#659
bjorkert wants to merge 1 commit into
devfrom
fix-la-ended-autorestart

Conversation

@bjorkert
Copy link
Copy Markdown
Member

Summary

When iOS reaches the Live Activity lifetime cap (~8h) before our renewal fires, it terminates the activity with .ended (not .dismissed). The attachStateObserver restart logic only ran on .dismissed, so after an iOS-initiated .ended:

  • handleForeground saw renewalFailed=false and renewBy still in the future → "no action needed".
  • startIfNeeded repeatedly re-bound the corpse activity (activities=1, current=nil after each bind → .ended → clear).
  • The Live Activity stayed dark on the lock screen / Dynamic Island until the user opened the app and pressed force-restart.

Fix

In attachStateObserver, when state == .ended for the currently-tracked activity and we didn't initiate the end ourselves (!endingForRestart), set laRenewalFailed = true. The next foreground entry then takes the existing renewal-failure path through handleForegroundperformForegroundRestart, which ends any leftover activities and pushes a fresh one.

Guards:

  • wasCurrentActivity — ignore late .ended deliveries from stale observers for activities we no longer track.
  • !endingForRestart — our own planned restarts already set the right flags downstream.
  • .dismissed branch is unchanged, so user-swipe handling is unaffected.

Repro

Real-world log from today shows the issue:

  • 10:18:57 — LA 6848B03C adopted, staleIn=26999s.
  • 18:18:57 — exactly 8h later: [LA] activity cleared id=… state=ended.
  • 18:18:57 → 18:27:30 — every foreground entry: foreground: no action needed (not in renewal window), startIfNeeded sees activities=1, current=nil, no restart fires.
  • 18:27:30 — user taps force-restart, new LA adopted, normal operation resumes.

When iOS reaches the Live Activity lifetime cap before renewal fires it
delivers .ended, not .dismissed. The state observer only ran restart
logic on .dismissed, so handleForeground saw renewalFailed=false and
renewBy still in the future and returned "no action needed", leaving
the LA dark until manual force-restart.

Mark laRenewalFailed=true on the .ended path (gated on wasCurrent and
!endingForRestart) so the next foreground entry triggers
performForegroundRestart, which sweeps the corpse activity and pushes a
fresh one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant