I'm developing a React Native iOS application that uses Live Activities. I've implemented the Live Activities functionality using native modules (Objective-C/Swift) to interact with the underlying iOS APIs. Updates to the Live Activity are triggered via APNs push notifications.
The problem I'm encountering is that the Live Activity updates are intermittent. Sometimes, when I send a push notification, the Live Activity updates correctly and displays the new information. Other times, the notification arrives (I can see it in the console logs or using a push notification testing tool), but the Live Activity remains unchanged. There's no consistent pattern to when the updates succeed or fail.
I've tried the following:
Verifying APNs Setup: I've double-checked my APNs setup, including the provisioning profiles, certificates, and the push notification payload. The payload includes the necessary
aps
dictionary with thecontent-available
key set to 1, and I'm also including thealert
key for testing (though I understand it's not strictly required for background updates). I've confirmed that the device receives the push notification reliably.Native Module Code Review: I've thoroughly reviewed the native module code that handles the Live Activity updates. I'm using
Activity.update
to update the activity's attributes. I've added logging to the native module to confirm that the update function is being called when a push notification is received. The logs show that the function is being called even when the Live Activity doesn't update, suggesting the issue might be with theActivity.update
call itself or something related to the timing of the update.Background Modes: I've enabled the "Background Modes" capability in Xcode for my app, specifically the "Background fetch" and "Remote notifications" options.
Testing with Different Devices/iOS Versions: I've tested on both physical devices and simulators running different iOS versions, but the intermittent behavior persists.
messaging().setBackgroundMessageHandler(async (remoteMessage) => { console.log('Notification.js: Your message was handled in background'); if (DynamicIslandModule && Platform.OS === "ios" && Platform.Version >= 16.1) { console.log("Notification.js true", remoteMessage); console.log(DynamicIslandModule, 'DynamicIslandModule'); console.log(remoteMessage?.data?.order, 'order id from background notification'); if (["void", "cancel", "bad", "complete"].includes(remoteMessage?.data?.title.toLowerCase())) { await DynamicIslandModule.endNotificationActivity(); } else { await DynamicIslandModule.updateNotificationActivityWithOrderStatus( remoteMessage?.data?.message, remoteMessage?.data?.order ); } } if (remoteMessage?.data?.notificationId) { console.log('Your message was handled in background'); let notificationId = remoteMessage?.data?.notificationId; await store.getLastSavedToken(); await singleton.markNotificationReceived({ _id: notificationId }); console.log( 'Message handled in the background!', remoteMessage?.data?.notificationId ); } });
-
//DynamicIslandModule.swift @objc(updateNotificationActivityWithOrderStatus:withOrderID:withOrderMessage:withTitle:withResolve:withReject:) func updateNotificationActivity( orderStatus: NSString, orderID: NSString, orderMessage: NSString, title: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { let status = orderStatus as String let orderIDString = orderID as String guard let storedIDs = UserDefaults.standard.array(forKey: "liveActivityIDs") as? [String], storedIDs.contains(orderIDString) else { print("ERROR: Live Activity ID not found in UserDefaults") return } guard let liveActivity = Activity<NotificationAttributes>.activities.first(where: { activity in if let contentState = activity.contentState as? NotificationAttributes.ContentState { return contentState.orderID == orderIDString } return false }) else { print("ERROR: Live Activity not found for ID: \(orderIDString)") return } guard let currentContentState = liveActivity.contentState as? NotificationAttributes.ContentState else { print("ERROR: Failed to retrieve current content state") return } let updatedContentState = NotificationAttributes.ContentState( orderStatus: status, orderID: orderIDString, pickupLocation: currentContentState.pickupLocation, dropoffLocation: currentContentState.dropoffLocation, serviceMethod: currentContentState.serviceMethod, orderMessage: orderMessage as String, title: title as String ) if #available(iOS 16.1, *) { print("Updating Live Activity...") Task { do { try await liveActivity.update(using: updatedContentState) resolve("Live Activity updated successfully for order #\(orderIDString)") print("Live Activity updated successfully for order #\(orderIDString) status:\(status)") } catch let error { reject("ERROR", "Failed to update live activity", error) print("Error updating live activity: \(error.localizedDescription)") } } } else { reject("ERROR", "iOS version not supported", nil) } }
-
//AppDelegate.mm - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { if (@available(iOS 16.1, *)) { NSString *orderID = userInfo[@"data"][@"order"]; NSString *orderMessage = userInfo[@"data"][@"message"]; NSString *orderStatus = userInfo[@"data"][@"status"]; NSString *orderTitle = userInfo[@"data"][@"title"]; NSLog(@"didReceiveRemoteNotification triggered"); NSArray *endTitles = @[@"complete", @"void", @"bad", @"cancel"]; if ([endTitles containsObject:orderStatus.lowercaseString]) { [LiveActivityHelper endActivityWithIdWithOrderID:orderID]; } else { [LiveActivityHelper updateActivityWithOrderID:orderID orderStatus:orderStatus orderMessage:orderMessage title:orderTitle]; } completionHandler(UIBackgroundFetchResultNewData); } else { completionHandler(UIBackgroundFetchResultNewData); } }
Question:
What could be causing these intermittent Live Activity updates? Are there any specific considerations regarding timing, background execution, or APNs payload structure that I might be missing? Any suggestions for debugging this further would be greatly appreciated.