I've having issues synchronizing user credentials between iOS and watchOS.
My basic set up is that my iOS app and watchOS app both need to talk to a backend server, and they both need an access token to do so. However, the user can only sign in on the iOS app (since user and password need to be typed in).
Now, when the user signs into iOS (or opens the app thereafter), I send the user's credentials to watchOS using updateApplicationContext(). A similar thing is done when the user logs out, as follows:
func logInOnWatch() {
if WCSession.isSupported() {
session.activate()
var userInfo = [String : Any]()
userInfo["type"] = "logInConfirm"
userInfo[userKey.isLoggedIn] = true
let deviceUserID = KeychainAccess.password(for: userKey.ID, service: userKey.keyChain)
userInfo[userKey.ID] = deviceUserID
let accessTokenSecret = KeychainAccess.password(for: userKey.accessTokenSecret, service: userKey.keyChain)
userInfo[userKey.accessTokenSecret] = accessTokenSecret
do {
try session.updateApplicationContext(userInfo)
}
catch {
print(error)
}
}
}
func logOutOnWatch() {
if WCSession.isSupported() {
session.activate()
var dict = [String : Any]()
dict["type"] = "logInConfirm"
dict["logInStatus"] = "User Has Not Logged In"
do {
try session.updateApplicationContext(dict)
}
catch {
print(error)
}
}
}
I then receive data and update the interface on the watch side as follows:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let contextType = applicationContext["type"] as? String {
switch contextType {
case "logInConfirm":
saveUserDataToDisk(from: applicationContext)
reloadRootControllersIfNeeded()
... // Handle other types of data
default:
return
}
}
}
func saveUserDataToDisk(from userInfo: [String : Any]) {
if let _ = userInfo[userKey.isLoggedIn] {
userDefaults.set(true, forKey: userKey.isLoggedIn)
if let token = userInfo[userKey.accessTokenSecret] as? String {
KeychainAccess.setPassword(token, for: userKey.accessTokenSecret, service: userKey.keyChain)
}
if let userID = userInfo[userKey.ID] as? String {
KeychainAccess.setPassword(userID, for: userKey.ID, service: userKey.keyChain)
}
}
else {
removeAllData()
}
}
func reloadRootControllersIfNeeded() {
if (userDefaults.object(forKey: userKey.isLoggedIn) == nil) {
/// Display the logged out controller if it isn't already the root controller.
if (loggedIn == nil) || loggedIn! {
WKInterfaceController.reloadRootControllers(withNames: ["LoggedOutController"], contexts: [self])
}
loggedIn = false
}
else {
/// Display the home controller if the logged out controller is the root controller.
if (loggedIn == nil) || !loggedIn! {
WKInterfaceController.reloadRootControllers(withNames: ["HomeController"], contexts: [self])
}
loggedIn = true
}
}
This all works fine when the user logs in and when the user logs out.
However, when using the app after having logged in, once in a while the watch switches the root controller to the logged out controller (presumably because it received a message telling it delete the logged in flag from user defaults, hence telling it to log out).
There isn't a good way to catch when this happens using Xcode, but it happens somewhat sporadically. Even worse, when I go on iOS to open the app (which calls logInOnWatch() from the tab bar controller's viewDidAppear), the watch isn't updated. The only way to then update the watch is to log out and log back in on iOS. Only then does the watch receive the credentials and the log in flag.
There are other instances throughout the life cycle of the app in which I send user data to watchOS from iOS once the user is logged in, and those work just fine. I'm wondering if it's some issue with how Apple is handling user defaults on the watch, or perhaps an issue with WatchConnectivity.
Is there a better way to keep this data synchronized more consistently?