Search code examples
iosswiftiphonepush-notificationactivitykit

Updating LiveActivities with Push notifications


I am trying to update the live activities with push notifications and I use the two methods one to start the live activity and the other to update the live activity which are as follows:

func startActivity(completion: @escaping (String) -> Void) {
        print("startActivity() is running")
        let attributes = PizzaDeliveryAttributes(numberOfPizzas: 2, totalAmount: "420", orderNumber: "359")

        let future = Calendar.current.date(byAdding: .minute, value: 35, to: Date())!.addingTimeInterval(40)
        let date = Date.now...future

        let initialContentState = PizzaDeliveryAttributes.ContentState(driverName: "Bill James", deliveryTimer:date)

        let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 30, to: Date())!)

        do {
            self.deliveryActivity = try Activity.request(attributes: attributes, content: activityContent, pushType: .token)
            print("Requested a pizza delivery Live Activity \(String(describing: self.deliveryActivity?.id)).")

            Task {
                for await data in self.deliveryActivity!.pushTokenUpdates {
                    let myToken = data.map {String(format: "%02x", $0)}.joined()
                    print("Token printed: \(myToken)")
                    completion(myToken)
                }
            }

        } catch (let error) {
            print("Error requesting pizza delivery Live Activity \(error.localizedDescription).")
        }
    }

    func updatePizzaDeliveryStatus(minutes: String, seconds: String) async {
        var future = Calendar.current.date(byAdding: .minute, value: (Int(minutes) ?? 0), to: Date())!
        future = Calendar.current.date(byAdding: .second, value: (Int(seconds) ?? 0), to: future)!
        let date = Date.now...future
        let updatedDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Anne Johnson", deliveryTimer: date)
        let alertConfiguration = AlertConfiguration(title: "Delivery update", body: "Your pizza order will arrive in 25 minutes.", sound: .default)
        let updatedContent = ActivityContent(state: updatedDeliveryStatus, staleDate: nil)

        do {
            try await self.deliveryActivity?.update(updatedContent, alertConfiguration: alertConfiguration)
        } catch {
            print("Failed to update Live Activity: \(error)")
        }
    }

I am able to generate pushTokens on every new live activity started and also send the curl command through to update the live activity which I will provide below. I expect the dynamic content of the attributes to update, but nothing happens when the push goes through.

curl -v \
--header "apns-topic:<Bundle-Id>.push-type.liveactivity" \
--header "apns-push-type:liveactivity" \
--header "authorization: bearer $AUTHENTICATION_TOKEN" \
--data \
'{"aps": {
   "timestamp":1663300480,
   "event": "update",
   "content-state": {
      "driverName": "Tony Stark",
      "deliveryTimer": {
        "start": 1663300480,       
        "end": 1663301480         
    }
   },
   "alert": {
      "title": "Race Update",
      "body": "Tony Stark is now leading the race!"
   }
}}' \
--http2 \
https://${APNS_HOST_NAME}/3/device/$DEVICE_TOKEN

And this is what content state in the attributes look like:

 public struct ContentState: Codable, Hashable {
            var driverName: String
            var deliveryTimer: ClosedRange<Date>
        }

Any help or insight is deeply appreciated.


Solution

  • When you run your curl, if you're getting a status code 200 response, then the push notifications are making it to the device.

    Assuming you're getting a 200, I can see it being two things.

    1. Issue with stale timestamps
    2. Issue with ContentState serializability

    Timestamp

    Your timestamp needs to be updated each time you send a push (can't send 2 pushes with the same timestamp). And the time has to be current. You can get that here.

    ContentState

    iOS is using codable to deserialize the push notification content-state into the struct ContentState. For this to work, the keys must match exactly for both. (I know this ContentState is from their example, but they use a simpler one for the push example)

    ClosedRange is codable, but I don't know what the keys are (possibly not start and end).

    Try putting only primitive types in your ContentState and see if that fixes it. If so, you can investigate the codability of ClosedRange. Let me know how that goes!

       "content-state": {
          "driverName": "Tony Stark",
          "deliveryStartInt": 1663300480,
          "deliveryEndInt": 1663301480,   
        }
       },
    
     public struct ContentState: Codable, Hashable {
                var driverName: String
                var deliveryStartInt: Int
                var deliveryEndInt: Int
            }