iosswiftwatchkitwatchoswatchconnectivity

WatchConnectivity: didReceiveApplicationContext never gets called although context is updated


I'm stuck with the following problem: I'm able to start WCSessions on both iPhone and Watch (simulator and real devices) and can use the sendMessage method just fine. I'm aware that updateApplicationContext requires an updated dict to be sent, so I'm adding an uuid for debugging purposes:

context = ["user":id,"messageId": UUID().uuidString]

There's no error thrown when the method is called but on the watch side didReceiveApplicationContext never gets called and the receivedApplicationContext dict stays empty all the time. After reading a lot of similar code examples and the docs I can't see where I'm wrong.

I'm building with XCode 12.5 and iOS 14.5 and watchOS 7.4

Here's the iOS code dealing with the WCSession, context is set in another method and is successfully transferred with sendMessage but not with updateApplicationContext:

import WatchConnectivity

public class CDVSettings : CDVPlugin, WCSessionDelegate {

var wcSession : WCSession! = nil

var didSendMessage:Bool = false

var context: [String : Any] = [:] {
    didSet {
        debugPrint("context didSet:", self.context)
        debugPrint("WCSession.isPaired: \(wcSession.isPaired), WCSession.isWatchAppInstalled: \(wcSession.isWatchAppInstalled)")
        if wcSession.activationState == WCSessionActivationState.activated {
            do {
                debugPrint("updateApplicationContext is called")
                self.didSendMessage=true
                try wcSession.updateApplicationContext(self.context)
            }
            catch let error as NSError {
                debugPrint(error.localizedDescription)
            }
            catch {}
            wcSession.sendMessage(self.context, replyHandler: { reply in
                print("Got reply: \(reply)")
            }, errorHandler: { error in
                print("error: \(error)")
            })
        } else {
            print("activationState is not activated")
            wcSession.activate()
        }
    }
}

public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    print("activationDidCompleteWith activationState:\(activationState) error:\(String(describing: error))")
}

public func sessionDidBecomeInactive(_ session: WCSession) {
    
}

public func sessionDidDeactivate(_ session: WCSession) {

}

@objc(pluginInitialize)
public override func pluginInitialize() {
    wcSession = WCSession.default
    wcSession.delegate = self
    wcSession.activate()
}

[...]

}

And here's the watch part:

    import WatchConnectivity
    
    class ConnectivityRequestHandler: NSObject, ObservableObject, WCSessionDelegate {
    
    var session = WCSession.default

    override init() {
        super.init()
        session.delegate = self
        session.activate()
        debugPrint("ConnectivityRequestHandler started with session", session)
    }
        
    // MARK: WCSession Methods
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        debugPrint(error)
        debugPrint("session is reachable:",session.isReachable)
        debugPrint("last received application context:",session.receivedApplicationContext)
    }
    
    
   func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
        debugPrint("didReceiveMessage: \(message)")
        replyHandler(["message received": Date()])
    }
    
    func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: @escaping (Data) -> Void) {
        debugPrint("didReceiveMessageData: \(messageData)")
    }
    
    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
        debugPrint("did receive application context")
        debugPrint(applicationContext)
            if let id = applicationContext["user"] as? [String]{
                debugPrint("\(id)")
                UserDefaults.standard.set(id, forKey: "user")
            }
        }

}

The corresponding logs on iOS

"context didSet:" ["user": "0e4a28b5-f8a0-40a5-942d-5f13b610a93a", "messageId": "8A78246C-91E6-48DC-B55D-4F4EBC761211"] "WCSession.isPaired: true, WCSession.isWatchAppInstalled: true" "updateApplicationContext is called" Got reply: ["message received": 2021-09-03 08:31:54 +0000]

and on watchOS

"ConnectivityRequestHandler started with session" <WCSession: 0x600003be0b40, hasDelegate: YES, activationState: 0> nil "session is reachable:" true "last received application context:" [:] "didReceiveMessage: ["user": 0e4a28b5-f8a0-40a5-942d-5f13b610a93a, "messageId": 8A78246C-91E6-48DC-B55D-4F4EBC761211]"

What am I doing wrong? Thank you so much for your help.


Solution

  • For those stumbling over my question while researching, it's no real solution, but an observation that I (now) share with others (see comments above): With real devices it works as expected, but you may need to reboot them in the process. In the simulators it seems that different methods may work while others won't and that even differs from developer to developer.