Debugging Notification delivery issues
Can you send Live Activity notifications to simulator?
Yes. The token created by the simulator works. Just don’t use the simulator for testing application lifecycle behavior or background tasks as a may not properly simulate OS restrictions.
Can you drag and drop payloads into the simulator?
That didn’t work for me.
How can you extract the key from the p8 file?
It’s just plain text. There’s no decryption required. You can open it with TextEdit
app.
How to correctly validate your json?
When you’re using Apple Push Notification Console, make sure you’re not using a timestamp
far in the past. 1-10 seconds is ok, but 50-200 seconds or more may cause the notification to not get sent.
- Attention: The console takes 2-3 seconds to figure out that your json is invalid. If you hit send before it errors out, then it would just send the last valid json. Or if you just pop back to the ‘fields view’ (as opposed to the json view) then it would NOT give you an error that the json was incorrect. You would be hitting send, thinking the json was updated, but it just defaults back to the last valid payload.
tldr make sure the json is correctly formed.
Which fields are necessary for a json payload to be valid?
For a start
event you need the following items:
- Set the value of the
event
field to"start"
. - Include an
alert
in the JSON payload. The alert portion has two purposes: - A payload with an alert, will have a sound and present the live activity in expanded mode.
- Its value is only used for Apple Watches. See here for how that works. For iPhones / iPads they are not used to change the UI, but only to decide if it should be expanded and alerting.
- Include the
attributes-type
andattributes
keys along with their necessary values. This is so that your app would know which type it must use to decode the payload.
⚠️ The Apple Docs’s sample payload uses an incorrect payload. Its
attribute
field should match the static portion of theActivityAttributes
⚠️
And like any other live activity payload you also need:
- The
contentState
- The
timestamp
Also note, I mainly used the Push Notification Console. Didn’t send jsons from command line or actual server. There are more nuances if you do it that way.
How can I be sure my encoding is done correct?
If your encoding is incorrect then the notification won’t get delivered.
Open a playground and just create the encoded json. Don’t try to do it manually by yourself. I was using an enum that had an associated value, the encoded wasn’t what I expected. For more on that see here
You can use the following wwdc code to get the json:
/// - Note: initialize your content accordingly.
let contentState = AdventureAttributes.ContentState(
currentHealthLevel: 0.941,
eventDescription: "Power Panda found a sword!"
)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(contentState)
print("\(String(data: json, encoding: .utf8)!)")
There’s a fixed amount budget that your app gets. You’ll get throttled if you don’t have any budget left.
Quoting from WWDC2023 - Update Live Activities with push notifications:
Using correct priority [for the
apns-priority
header] ensures you have budget left when you need it.
The priority you should always consider using first is low priority. Low priority updates are delivered opportunistically, which lowers the impact on the user’s battery life. However, this means the Live Activities might not be updated immediately when the push request is sent. So you should use low priority for updates that are less time-sensitive. Another benefit of using low priority is that there is no limit on how many updates you can send. In order to take advantage of this, you should be using low priority for the majority of your Live Activity updates.
On the other hand, certain updates require the user’s immediate attention, in these cases, choose high priority updates. High priority updates are delivered immediately. That’s why they’re perfect for time-sensitive updates. However, due to their impact on the user’s battery life, the system imposes a budget depending on the device condition. [To be able to inspect the true impact of notifications on your budget, try testing mac os console wirelessly and with a low battery or maybe even with “low power mode”.]without having the device plugged in
If your app exceeds its budget, the system will throttle your push updates, and it will dramatically impact your user experience. If you need the server to send high-priority pushes frequently to keep it up to date then add the
NSSupportsLiveActivitiesFrequentUpdates
flag to your plist with aYES
value. Users can disable frequent updates independently of Live Activities in Settings. So you can must detect the status of the frequent updates feature by accessing theActivityAuthorizationInfo
frequentPushesEnabled
property.
Other reasons for lack of delivery
If all that didn’t work and you still need to log at other issues then, see Apple Developer’s Answer - Rico
Connect your phone to the macOS and sift through the logs by:
Filter by Budget and if you see :
priority(0)
,budget(0)
orrunning-not-visible
, the system has decided to not show your activity. Some reasons why this may occur:
- low battery mode
- bad data in the push(es)
- too many pushes in a short period of time
To fix the budgeting issue, you may be able to remedy this by using NSSupportsLiveActivitiesFrequentUpdates
which then gives you more budget.
Getting more information from the macOS Console
.
You can debug much of this using Console.app. The behavior is not entirely consistent across OS versions but nonetheless you can debug to see if your process has lost budget or something else leading to your token not being sent.
A good way to start would be with filtering by the BundleID of the target and / or the target name itself (the process name).
Also be sure to check the system processes for additional information. This will vary based on what and where you are looking but some examples:
- To debug APNS you’ll want to monitor
apnd
along with other relevant processes. - To debug WidgetKit, LiveActivities, Dynamic Island you’ll want to monitor
springboardd
,liveactivitiesd
along with other relevant processes.
A Daemon is a background Process without any user interface. To find a comprehensive list of Apple Daemons see this unofficial list.
Notification delivery in Debug builds
Debug builds may tamper the delivery of notifications.
the debugger loads with a development profile and is not exactly the same, particularly with notification delivery.
from forums
I’ve experienced scenarios were push notifications didn’t deliver and I had to switch from WiFi to Cellular or restart my iPhone or changes networks. Not sure if the root cause was that I was using a debug build or not 🤷.
Code after for loop await may never run!
As someone who’s not super savvy with async await I learned that:
for await ptsToken in Activity<DeviceActivationAttributes>.pushToStartTokenUpdates {
...
}
doSomething()
In this setup, doSomething()
is never called.
A friend explained that this is because pushToStartTokenUpdates
conforms to AsyncSequence
. An AsyncSequence
provides an AsyncIterator
, which exposes a next()
method that asynchronously returns the next element in the sequence as an optional (Element?
). The loop continues until next()
returns nil
, signaling the end of the sequence. Since the for await
loop waits for the sequence to finish, and this particular sequence never ends, execution never reaches doSomething()
.
This Avander Lee article describes how that works with some more detail.
For what it’s worth, even if the sequence emits a finite number of values, it can still take a very long time to complete. For example, if new values are emitted every 30 minutes, it would take 2.5 hours for the loop to finish processing 5 values.
In the case of push-to-start token updates or token updates stream, the sequence emits an indefinite number of values, meaning it never ends. As a result, your for await loop will keep running indefinitely. This also means that token updates can trigger your app to launch or resume in the background at unpredictable times, depending on when those updates occur.
What should I do if my images don’t appear in the leading / trailing views?
- Images have to be less than
4kb
and shouldn’t exceed45x36.67
points. - Images can often not be hard to see, because of their color. Had to do something like:
Image(contentState.imageName)
.renderingMode(.template) // this was critical for resizing
.resizable()
.frame(width: 40, height: 40)
.foregroundStyle(.yellow)
I can’t see logs for the widget. What can I do?
Use OSLog
and sift through the logs.
Generally I have a hard time getting logs from Xcode for App Extensions. But easy to use from macos Console app.
I’m struggling up to come up with a good design. What should I do?
In short Apple suggests that the design should be bold, unique and glanceable. The - WWDC 2023 - Design dynamic Live Activities is a fantastic talk. If you have doubts, then try to take inspiration from Apple Clock, Uber, Apple Maps or Google Maps. You can find a nice variation of design among them.
I can’t find the answer I’m looking for. What should I do?
There’s lot of other stuff mentioned in the forums. You really have to go digging. I’ve also even gone far as to open a DTS ticket. Getting support from an Apple Engineer for $70 is great value for your money. Especially that you can send them code where they can take a look at a later time and get back to you. Only note is, each time it takes 4-7 days for them to get back to you. So make sure you include all the necessary details.
Shout outs
Special shout out to Kevin Lee who reviewed the series. Suggested that I upload my blog to iOS Dev Weekly to boost my reach and has always been helping me push forward.
Series Summary
Understanding the architecture is possibly the most complex. To know how to react to callbacks in a way that works for when the app is launched into background vs how to react to callbacks that just resume the app after it was suspended. Also figuring how the server architecture is a challenging task for engineers. Hopefully the series gave you some ideas on how to tackle these problems.