Skip to content

Commit 90d9ddf

Browse files
authored
Watch app: Fix connection issue upon fresh installs (#15867)
2 parents ca67d0f + c79e386 commit 90d9ddf

File tree

4 files changed

+54
-18
lines changed

4 files changed

+54
-18
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [*] POS: icon button with confirmation step used for clearing the cart [https://github.com/woocommerce/woocommerce-ios/pull/15829]
88
- [*] Shipping Labels: Fixed a portion of layout issues caused by bigger accessibility content size categories. [https://github.com/woocommerce/woocommerce-ios/pull/15844]
99
- [*] Shipping Labels: Enable the confirm button on the payment method sheet even when there are no changes. [https://github.com/woocommerce/woocommerce-ios/pull/15856]
10+
- [*] Watch app: Fixed connection issue upon fresh install [https://github.com/woocommerce/woocommerce-ios/pull/15867]
1011

1112
22.7
1213
-----

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,7 @@ enum WooAnalyticsStat: String {
12481248
case watchAppOpened = "watch_app_opened"
12491249
case watchStoreDataSynced = "watch_store_data_synced"
12501250
case watchConnectingOpened = "watch_connecting_opened"
1251+
case watchSyncingFailed = "watch_syncing_failed"
12511252
case watchMyStoreOpened = "watch_my_store_opened"
12521253
case watchOrdersListOpened = "watch_orders_list_opened"
12531254
case watchPushNotificationTapped = "watch_push_notification_tapped"

WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import WatchConnectivity
22
import Combine
33
import Networking
4+
import protocol WooFoundation.Analytics
45
import class WooFoundation.CurrencySettings
56

67
/// Type that syncs the necessary dependencies to the watch session.
@@ -10,6 +11,8 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
1011
/// Current WatchKit Session
1112
private let watchSession: WCSession
1213

14+
private let analytics: Analytics
15+
1316
/// Subscriptions store for combine publishers
1417
///
1518
private var subscriptions = Set<AnyCancellable>()
@@ -34,16 +37,15 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
3437
///
3538
@Published var account: Account?
3639

37-
/// Tracks if the current watch session is active or not
38-
///
39-
@Published private var isSessionActive: Bool = false
40-
4140
/// Toggle this value to force a credentials sync.
4241
///
4342
@Published private var syncTrigger = false
4443

45-
init(watchSession: WCSession = WCSession.default, storedDependencies: WatchDependencies?) {
44+
init(watchSession: WCSession = WCSession.default,
45+
storedDependencies: WatchDependencies?,
46+
analytics: Analytics = ServiceLocator.analytics) {
4647
self.watchSession = watchSession
48+
self.analytics = analytics
4749
super.init()
4850

4951
self.storeID = storedDependencies?.storeID
@@ -80,9 +82,7 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
8082
let (storeID, storeName, credentials, currencySettings) = required
8183
let (enablesCrashReports, account) = configuration
8284

83-
guard let storeID, let storeName, let credentials else {
84-
return nil
85-
}
85+
guard let storeID, let storeName, let credentials else { return nil }
8686

8787
return .init(storeID: storeID,
8888
storeName: storeName,
@@ -95,16 +95,30 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
9595
.debounce(for: 0.5, scheduler: DispatchQueue.main)
9696

9797
// Syncs the dependencies to the paired counterpart when the session becomes available.
98-
Publishers.CombineLatest3(watchDependencies, $isSessionActive, $syncTrigger)
99-
.sink { [watchSession] dependencies, isSessionActive, forceSync in
98+
watchDependencies.combineLatest($syncTrigger)
99+
.sink { [weak self, watchSession] dependencies, forceSync in
100100

101101
// Do not update the context if the session is not active, the watch is not paired or the watch app is not installed.
102-
guard isSessionActive, watchSession.isPaired, watchSession.isWatchAppInstalled else { return }
102+
guard watchSession.activationState == .activated,
103+
watchSession.isPaired,
104+
watchSession.isWatchAppInstalled else {
105+
self?.analytics.track(
106+
.watchSyncingFailed,
107+
properties: [
108+
"session_active": watchSession.activationState == .activated,
109+
"session_paired": watchSession.isPaired,
110+
"watch_app_installed": watchSession.isWatchAppInstalled
111+
],
112+
error: SyncError.watchSessionInactiveOrNotPaired
113+
)
114+
return
115+
}
103116

104117
do {
105118

106119
// If dependencies is nil, send an empty dictionary. This is most likely a logged out state
107120
guard let dependencies else {
121+
self?.analytics.track(.watchSyncingFailed, withError: SyncError.noDependenciesFound)
108122
return try watchSession.updateApplicationContext([:])
109123
}
110124

@@ -118,9 +132,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
118132

119133
try watchSession.updateApplicationContext(jsonObject)
120134
} else {
135+
self?.analytics.track(.watchSyncingFailed, withError: SyncError.encodingApplicationContextFailed)
121136
DDLogError("⛔️ Unable to encode watch dependencies for synchronization. Resulting object is not a dictionary")
122137
}
123138
} catch {
139+
self?.analytics.track(.watchSyncingFailed, withError: error)
124140
DDLogError("⛔️ Error synchronizing credentials into watch session: \(error)")
125141
}
126142
}
@@ -129,7 +145,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
129145

130146
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
131147
DDLogInfo("🔵 WatchSession activated \(activationState)")
132-
self.isSessionActive = activationState == .activated
133148
}
134149

135150
func sessionDidBecomeInactive(_ session: WCSession) {
@@ -138,7 +153,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
138153

139154
func sessionDidDeactivate(_ session: WCSession) {
140155
// Try to guarantee an active session
141-
self.isSessionActive = false
142156
watchSession.activate()
143157
}
144158
}
@@ -149,13 +163,12 @@ extension WatchDependenciesSynchronizer {
149163
/// This is in order to not duplicate tracks configuration which involve quite a lot of information to be transmitted to the watch.
150164
///
151165
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
152-
153166
// The user info could contain a track event. Send it if we found one.
154167
guard let rawEvent = userInfo[WooConstants.watchTracksKey] as? String,
155168
let analyticEvent = WooAnalyticsStat(rawValue: rawEvent) else {
156169
return DDLogError("⛔️ Unsupported watch tracks event: \(userInfo)")
157170
}
158-
ServiceLocator.analytics.track(analyticEvent)
171+
analytics.track(analyticEvent)
159172
}
160173
}
161174

@@ -173,7 +186,14 @@ extension WatchDependenciesSynchronizer {
173186
guard message[WooConstants.watchSyncKey] as? Bool == true else {
174187
return DDLogError("⛔️ Unsupported sync request message: \(message)")
175188
}
176-
177189
syncTrigger.toggle()
178190
}
179191
}
192+
193+
extension WatchDependenciesSynchronizer {
194+
enum SyncError: Error {
195+
case watchSessionInactiveOrNotPaired
196+
case noDependenciesFound
197+
case encodingApplicationContextFailed
198+
}
199+
}

WooCommerce/Woo Watch App/ConnectView.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ struct ConnectView: View {
77

88
@EnvironmentObject private var tracksProvider: WatchTracksProvider
99

10+
@State private var didRequestSyncing = false
11+
1012
let synchronizer: PhoneDependenciesSynchronizer
1113

1214
let message: String = Localization.connectMessage
@@ -18,6 +20,12 @@ struct ConnectView: View {
1820
.multilineTextAlignment(.center)
1921
.fixedSize(horizontal: false, vertical: true)
2022

23+
if didRequestSyncing {
24+
Text(Localization.workaroundTip)
25+
.multilineTextAlignment(.center)
26+
.fixedSize(horizontal: false, vertical: true)
27+
}
28+
2129
Image(systemName: "bolt.fill")
2230
.renderingMode(.original)
2331
.resizable()
@@ -26,6 +34,7 @@ struct ConnectView: View {
2634

2735
Button(Localization.itsNotWorking) {
2836
synchronizer.requestCredentialSync()
37+
didRequestSyncing = true
2938
}
3039
}
3140
}
@@ -44,10 +53,15 @@ extension ConnectView {
4453

4554
private enum Localization {
4655
static let connectMessage = AppLocalizedString(
47-
"watch.connect.message",
48-
value: "Open Woo on your iPhone, connect your store, and hold your Watch nearby",
56+
"watch.connect.messageUpdated",
57+
value: "Open Woo on your iPhone, log into your store, and hold your Watch nearby.",
4958
comment: "Info message when connecting your watch to the phone for the first time."
5059
)
60+
static let workaroundTip = AppLocalizedString(
61+
"watch.connect.workaround",
62+
value: "If the error persists, relaunch the app.",
63+
comment: "Workaround when connecting the watch to the phone fails."
64+
)
5165
static let itsNotWorking = AppLocalizedString(
5266
"watch.connect.notworking.title",
5367
value: "It's not working",

0 commit comments

Comments
 (0)