🐛 Fix watch connectivty didn't work on real devices

This commit is contained in:
2025-11-01 02:38:53 +08:00
parent a2b0cd0b6a
commit 01cc71fd47
4 changed files with 100 additions and 76 deletions

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -182,6 +182,8 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = "WatchRunner Watch App";
sourceTree = "<group>";
};
@@ -669,14 +671,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -734,14 +732,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -792,14 +786,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";

View File

@@ -5,14 +5,14 @@ import WatchConnectivity
@main
@objc class AppDelegate: FlutterAppDelegate {
let notifyDelegate = NotifyDelegate()
private var watchConnectivityService: WatchConnectivityService?
private static var sharedWatchConnectivityService: WatchConnectivityService?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = notifyDelegate
let replyableMessageCategory = UNNotificationCategory(
identifier: "CHAT_MESSAGE",
actions: [
@@ -25,38 +25,45 @@ import WatchConnectivity
intentIdentifiers: [],
options: []
)
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
GeneratedPluginRegistrant.register(with: self)
// Always initialize and retain a strong reference
if WCSession.isSupported() {
watchConnectivityService = WatchConnectivityService()
AppDelegate.sharedWatchConnectivityService = WatchConnectivityService.shared
} else {
print("[iOS] WCSession not supported on this device.")
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
class WatchConnectivityService: NSObject, WCSessionDelegate {
private let session: WCSession
override init() {
self.session = .default
final class WatchConnectivityService: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityService()
private let session: WCSession = .default
private override init() {
super.init()
print("[iOS] Activating WCSession")
self.session.delegate = self
self.session.activate()
print("[iOS] Activating WCSession...")
session.delegate = self
session.activate()
}
// MARK: - WCSessionDelegate
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("[iOS] WCSession activation failed with error: \(error.localizedDescription)")
return
print("[iOS] WCSession activation failed: \(error.localizedDescription)")
} else {
print("[iOS] WCSession activated with state: \(activationState.rawValue)")
if activationState == .activated {
sendDataToWatch()
}
}
print("[iOS] WCSession activated with state: \(activationState.rawValue)")
}
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {
@@ -69,10 +76,7 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
let token = UserDefaults.standard.getFlutterToken()
let serverUrl = UserDefaults.standard.getServerUrl()
print("[iOS] Retrieved token: \(token ?? "nil")")
print("[iOS] Retrieved serverUrl: \(serverUrl)")
var data: [String: Any] = ["serverUrl": serverUrl]
var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
if let token = token {
data["token"] = token
}
@@ -81,4 +85,25 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
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)")
}
}
}

View File

@@ -1,15 +1,6 @@
//
// WatchConnectivityService.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
import WatchConnectivity
import Combine
// MARK: - Watch Connectivity
import Foundation
class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
@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]) {
print("[watchOS] Received message: \(message)")
DispatchQueue.main.async {
@@ -64,41 +71,43 @@ class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
}
func requestDataFromPhone() {
if self.isFetched == true {
print("[watchOS] Skipped fetch from phone due to tried.")
return
// Check if we already have valid data to avoid unnecessary requests
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
}
guard session.activationState == .activated else {
print("[watchOS] Session not activated yet, state: \(session.activationState.rawValue)")
DispatchQueue.main.async {
self.errorMessage = "Session not ready yet"
}
guard session.isReachable else {
return
}
print("[watchOS] Requesting data from phone")
session.sendMessage(["request": "data"]) { [weak self] response in
guard let self = self else { return }
print("[watchOS] Received reply: \(response)")
DispatchQueue.main.async {
self.isFetched = true
let errorMsg = "Phone is not reachable"
print("[watchOS] \(errorMsg)")
DispatchQueue.main.async {
self.errorMessage = errorMsg
if let token = response["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
return
if let serverUrl = response["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
self.errorMessage = nil // Clear any previous errors
}
print("[watchOS] Requesting data from phone")
session.sendMessage(["request": "data"]) { [weak self] response in
guard let self = self else { return }
print("[watchOS] Received reply: \(response)")
DispatchQueue.main.async {
self.isFetched = true
if let token = response["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = response["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
}
} errorHandler: { error in
print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
DispatchQueue.main.async {
self.errorMessage = "Failed to get data from phone: \(error.localizedDescription)"
}
} errorHandler: { error in
print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
DispatchQueue.main.async {
self.errorMessage = "Failed to get data from phone: \(error.localizedDescription)"
// Don't set isFetched = true on error - allow retry
}
}
}
}

View File

@@ -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
# 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.
version: 3.3.0+140
version: 3.3.0+142
environment:
sdk: ^3.7.2