🐛 Fix watch connectivty didn't work on real devices
This commit is contained in:
		@@ -3,7 +3,7 @@
 | 
				
			|||||||
	archiveVersion = 1;
 | 
						archiveVersion = 1;
 | 
				
			||||||
	classes = {
 | 
						classes = {
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	objectVersion = 77;
 | 
						objectVersion = 54;
 | 
				
			||||||
	objects = {
 | 
						objects = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Begin PBXBuildFile section */
 | 
					/* Begin PBXBuildFile section */
 | 
				
			||||||
@@ -182,6 +182,8 @@
 | 
				
			|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
 | 
					/* Begin PBXFileSystemSynchronizedRootGroup section */
 | 
				
			||||||
		7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */ = {
 | 
							7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */ = {
 | 
				
			||||||
			isa = PBXFileSystemSynchronizedRootGroup;
 | 
								isa = PBXFileSystemSynchronizedRootGroup;
 | 
				
			||||||
 | 
								exceptions = (
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
			path = "WatchRunner Watch App";
 | 
								path = "WatchRunner Watch App";
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
@@ -669,14 +671,10 @@
 | 
				
			|||||||
			inputFileListPaths = (
 | 
								inputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			inputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			name = "[CP] Copy Pods Resources";
 | 
								name = "[CP] Copy Pods Resources";
 | 
				
			||||||
			outputFileListPaths = (
 | 
								outputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			outputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
								runOnlyForDeploymentPostprocessing = 0;
 | 
				
			||||||
			shellPath = /bin/sh;
 | 
								shellPath = /bin/sh;
 | 
				
			||||||
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
 | 
								shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
 | 
				
			||||||
@@ -734,14 +732,10 @@
 | 
				
			|||||||
			inputFileListPaths = (
 | 
								inputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			inputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			name = "[CP] Embed Pods Frameworks";
 | 
								name = "[CP] Embed Pods Frameworks";
 | 
				
			||||||
			outputFileListPaths = (
 | 
								outputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			outputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
								runOnlyForDeploymentPostprocessing = 0;
 | 
				
			||||||
			shellPath = /bin/sh;
 | 
								shellPath = /bin/sh;
 | 
				
			||||||
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
 | 
								shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
 | 
				
			||||||
@@ -792,14 +786,10 @@
 | 
				
			|||||||
			inputFileListPaths = (
 | 
								inputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			inputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			name = "[CP] Embed Pods Frameworks";
 | 
								name = "[CP] Embed Pods Frameworks";
 | 
				
			||||||
			outputFileListPaths = (
 | 
								outputFileListPaths = (
 | 
				
			||||||
				"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 | 
									"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			outputPaths = (
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
								runOnlyForDeploymentPostprocessing = 0;
 | 
				
			||||||
			shellPath = /bin/sh;
 | 
								shellPath = /bin/sh;
 | 
				
			||||||
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";
 | 
								shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import WatchConnectivity
 | 
				
			|||||||
@main
 | 
					@main
 | 
				
			||||||
@objc class AppDelegate: FlutterAppDelegate {
 | 
					@objc class AppDelegate: FlutterAppDelegate {
 | 
				
			||||||
    let notifyDelegate = NotifyDelegate()
 | 
					    let notifyDelegate = NotifyDelegate()
 | 
				
			||||||
    private var watchConnectivityService: WatchConnectivityService?
 | 
					    private static var sharedWatchConnectivityService: WatchConnectivityService?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override func application(
 | 
					    override func application(
 | 
				
			||||||
        _ application: UIApplication,
 | 
					        _ application: UIApplication,
 | 
				
			||||||
@@ -25,36 +25,43 @@ import WatchConnectivity
 | 
				
			|||||||
            intentIdentifiers: [],
 | 
					            intentIdentifiers: [],
 | 
				
			||||||
            options: []
 | 
					            options: []
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
 | 
					        UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        GeneratedPluginRegistrant.register(with: self)
 | 
					        GeneratedPluginRegistrant.register(with: self)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Always initialize and retain a strong reference
 | 
				
			||||||
        if WCSession.isSupported() {
 | 
					        if WCSession.isSupported() {
 | 
				
			||||||
            watchConnectivityService = WatchConnectivityService()
 | 
					            AppDelegate.sharedWatchConnectivityService = WatchConnectivityService.shared
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            print("[iOS] WCSession not supported on this device.")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
 | 
					        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WatchConnectivityService: NSObject, WCSessionDelegate {
 | 
					final class WatchConnectivityService: NSObject, WCSessionDelegate {
 | 
				
			||||||
    private let session: WCSession
 | 
					    static let shared = WatchConnectivityService()
 | 
				
			||||||
 | 
					    private let session: WCSession = .default
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    override init() {
 | 
					    private override init() {
 | 
				
			||||||
        self.session = .default
 | 
					 | 
				
			||||||
        super.init()
 | 
					        super.init()
 | 
				
			||||||
        print("[iOS] Activating WCSession")
 | 
					        print("[iOS] Activating WCSession...")
 | 
				
			||||||
        self.session.delegate = self
 | 
					        session.delegate = self
 | 
				
			||||||
        self.session.activate()
 | 
					        session.activate()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: - WCSessionDelegate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
 | 
					    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
 | 
				
			||||||
        if let error = error {
 | 
					        if let error = error {
 | 
				
			||||||
            print("[iOS] WCSession activation failed with error: \(error.localizedDescription)")
 | 
					            print("[iOS] WCSession activation failed: \(error.localizedDescription)")
 | 
				
			||||||
            return
 | 
					        } else {
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
            print("[iOS] WCSession activated with state: \(activationState.rawValue)")
 | 
					            print("[iOS] WCSession activated with state: \(activationState.rawValue)")
 | 
				
			||||||
 | 
					            if activationState == .activated {
 | 
				
			||||||
 | 
					                sendDataToWatch()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    func sessionDidBecomeInactive(_ session: WCSession) {}
 | 
					    func sessionDidBecomeInactive(_ session: WCSession) {}
 | 
				
			||||||
@@ -69,10 +76,7 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
 | 
				
			|||||||
            let token = UserDefaults.standard.getFlutterToken()
 | 
					            let token = UserDefaults.standard.getFlutterToken()
 | 
				
			||||||
            let serverUrl = UserDefaults.standard.getServerUrl()
 | 
					            let serverUrl = UserDefaults.standard.getServerUrl()
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            print("[iOS] Retrieved token: \(token ?? "nil")")
 | 
					            var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
 | 
				
			||||||
            print("[iOS] Retrieved serverUrl: \(serverUrl)")
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var data: [String: Any] = ["serverUrl": serverUrl]
 | 
					 | 
				
			||||||
            if let token = token {
 | 
					            if let token = token {
 | 
				
			||||||
                data["token"] = token
 | 
					                data["token"] = token
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -81,4 +85,25 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
 | 
				
			|||||||
            replyHandler(data)
 | 
					            replyHandler(data)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func sendDataToWatch() {
 | 
				
			||||||
 | 
					        guard session.activationState == .activated else {
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let token = UserDefaults.standard.getFlutterToken()
 | 
				
			||||||
 | 
					        let serverUrl = UserDefaults.standard.getServerUrl()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
 | 
				
			||||||
 | 
					        if let token = token {
 | 
				
			||||||
 | 
					            data["token"] = token
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        do {
 | 
				
			||||||
 | 
					            try session.updateApplicationContext(data)
 | 
				
			||||||
 | 
					            print("[iOS] Sent application context: \(data)")
 | 
				
			||||||
 | 
					        } catch {
 | 
				
			||||||
 | 
					            print("[iOS] Failed to send application context: \(error.localizedDescription)")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,6 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
//  WatchConnectivityService.swift
 | 
					 | 
				
			||||||
//  WatchRunner Watch App
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  Created by LittleSheep on 2025/10/29.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Foundation
 | 
					 | 
				
			||||||
import WatchConnectivity
 | 
					import WatchConnectivity
 | 
				
			||||||
import Combine
 | 
					import Combine
 | 
				
			||||||
 | 
					import Foundation
 | 
				
			||||||
// MARK: - Watch Connectivity
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
 | 
					class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
 | 
				
			||||||
    @Published var token: String?
 | 
					    @Published var token: String?
 | 
				
			||||||
@@ -49,6 +40,22 @@ class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
 | 
				
			||||||
 | 
					        print("[watchOS] Received application context: \(applicationContext)")
 | 
				
			||||||
 | 
					        DispatchQueue.main.async {
 | 
				
			||||||
 | 
					            if let token = applicationContext["token"] as? String {
 | 
				
			||||||
 | 
					                self.token = token
 | 
				
			||||||
 | 
					                self.userDefaults.set(token, forKey: self.tokenKey)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if let serverUrl = applicationContext["serverUrl"] as? String {
 | 
				
			||||||
 | 
					                self.serverUrl = serverUrl
 | 
				
			||||||
 | 
					                self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            self.isFetched = true
 | 
				
			||||||
 | 
					            self.errorMessage = nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
 | 
					    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
 | 
				
			||||||
        print("[watchOS] Received message: \(message)")
 | 
					        print("[watchOS] Received message: \(message)")
 | 
				
			||||||
        DispatchQueue.main.async {
 | 
					        DispatchQueue.main.async {
 | 
				
			||||||
@@ -64,17 +71,17 @@ class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    func requestDataFromPhone() {
 | 
					    func requestDataFromPhone() {
 | 
				
			||||||
        if self.isFetched == true {
 | 
					    // Check if we already have valid data to avoid unnecessary requests
 | 
				
			||||||
            print("[watchOS] Skipped fetch from phone due to tried.")
 | 
					    if let token = self.token, let serverUrl = self.serverUrl, !token.isEmpty, !serverUrl.isEmpty {
 | 
				
			||||||
 | 
					        print("[watchOS] Skipped fetch - already have valid data")
 | 
				
			||||||
 | 
					        self.isFetched = true
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        guard session.isReachable else {
 | 
					    guard session.activationState == .activated else {
 | 
				
			||||||
            self.isFetched = true
 | 
					        print("[watchOS] Session not activated yet, state: \(session.activationState.rawValue)")
 | 
				
			||||||
            let errorMsg = "Phone is not reachable"
 | 
					 | 
				
			||||||
            print("[watchOS] \(errorMsg)")
 | 
					 | 
				
			||||||
        DispatchQueue.main.async {
 | 
					        DispatchQueue.main.async {
 | 
				
			||||||
                self.errorMessage = errorMsg
 | 
					            self.errorMessage = "Session not ready yet"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -93,12 +100,14 @@ class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
 | 
				
			|||||||
                self.serverUrl = serverUrl
 | 
					                self.serverUrl = serverUrl
 | 
				
			||||||
                self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
 | 
					                self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            self.errorMessage = nil // Clear any previous errors
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } errorHandler: { error in
 | 
					    } errorHandler: { error in
 | 
				
			||||||
        print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
 | 
					        print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
 | 
				
			||||||
        DispatchQueue.main.async {
 | 
					        DispatchQueue.main.async {
 | 
				
			||||||
            self.errorMessage = "Failed to get data from phone: \(error.localizedDescription)"
 | 
					            self.errorMessage = "Failed to get data from phone: \(error.localizedDescription)"
 | 
				
			||||||
            }
 | 
					            // Don't set isFetched = true on error - allow retry
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
 | 
				
			|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
					# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
				
			||||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
					# In Windows, build-name is used as the major, minor, and patch parts
 | 
				
			||||||
# of the product and file versions while build-number is used as the build suffix.
 | 
					# of the product and file versions while build-number is used as the build suffix.
 | 
				
			||||||
version: 3.3.0+140
 | 
					version: 3.3.0+142
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.7.2
 | 
					  sdk: ^3.7.2
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user