5

I have just set up WCConnectivity in my app using tutorials/sample code and I feel I have it implemented correctly. I am not using the simulator to test. For some reason, the didReceiveApplicationContext is not being called in the watch app, even though everything is set up correctly.

I've tried calling in the the Interface Controller of the WatchKit app, in the ExtensionDelegate and using NSUserDefaults to set the Interface Controller data instead.

iOS App

ViewController.m

- (void) viewDidLoad{
if ([WCSession isSupported]) {
    WCSession *session = [WCSession defaultSession];
    session.delegate = self;
    [session activateSession];
  }
}

-(void) saveTrip{
NSMutableArray *currentTrips = [NSMutableArray arrayWithArray:[self.sharedDefaults objectForKey:@"UserLocations"]];

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:newLocation];
[currentTrips addObject:data];
[self.sharedDefaults setObject:currentTrips forKey:@"UserLocations"];
[self.sharedDefaults synchronize];

WCSession *session = [WCSession defaultSession];
NSDictionary *applicationDict = [[NSDictionary alloc] initWithObjects:@[currentTrips] forKeys:@[@"UserLocations"]];;
[session updateApplicationContext:applicationDict error:nil];
}

Watch Extension Code

ExtensionDelegate.m

- (void)applicationDidFinishLaunching {
if ([WCSession isSupported]) {
    WCSession *session = [WCSession defaultSession];
    session.delegate = self;
    [session activateSession];
}
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
self.places= [applicationContext objectForKey:@"UserLocations"];
[[NSUserDefaults standardUserDefaults] setObject:self.places forKey:@"UserLocations"];
[[NSUserDefaults standardUserDefaults] synchronize]; 
}

InterfaceController.m

- (void)willActivate {
[super willActivate];
self.placesData = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@"UserLocations"]];
[self loadData];
}
emleeh
  • 106
  • 1
  • 5

4 Answers4

24

For me it was two things:

  1. Passing invalid values in the dictionary. Can't even pass NSNull() to represent nil values. If you have data in there that can't be represented in a plist, it fails.

  2. If the dictionary doesn't change, subsequent calls to updateApplicationContext won't trigger a corresponding call to didReceiveApplicationContext. To force an update—perhaps in debug builds—you could add a UUID to the payload, e.g.

    context["force_send"] = UUID().uuidString

Graham Perks
  • 23,007
  • 8
  • 61
  • 83
  • 5
    This is the most important finding! The applicationContext is buffered on the iOS device, so it is only transferred to the watch extension once if it doesn't change. In test situations, it is very helpful to add an 'changer' (like the UUID solution, or - maybe even better - a NSDate.date). – LaborEtArs Mar 20 '18 at 07:10
5

It might prove useful to handle any errors, so change:

[session updateApplicationContext:applicationDict error:nil];

to

NSError *error;    
if (![session updateApplicationContext:applicationDict error:&error]) {
    NSLog(@"updateApplicationContext failed with error %@", error);
}
ccjensen
  • 4,578
  • 2
  • 23
  • 25
2

If it's not working, it's probably not implemented correctly.

A few issues at first glance:

  • In your iOS app, you get a reference to the shared session and activate it, but don't assign that local variable to any property on your view controller. This means your local variable will go out of scope, once viewDidLoad exits. You also need to correct that in your watch extension.

  • In saveTrip, you again create another local session, which isn't activated and doesn't have any delegate. You need to use the first session that you set up and activated earlier.

  • On your watch, you save data that is received but your interface controller won't know that there is new data that it should load and display.

A few tips:

  • If you setup and activate the session in application:didFinishLaunchingWithOptions, it will be available app-wide (and activated much earlier in your app's lifecycle).

  • You should check that your session is valid, as a watch may not be paired, or the watch app may not be installed. Here's a good (Swift) tutorial that covers those cases, and also uses a session manager to make it easier to support using Watch Connectivity throughout your app.

  • You may want to pass less/lighter data across to the watch, than trying to deal with archiving an array of custom objects.

  • As an aside, what you're trying to do with NSUserDefaults is very convoluted. It's really meant to persist preferences across launches. It's not appropriate to misuse it as a way to pass your model back and forth between your extension and controller.

  • Thanks. I'm only using NSUserDefaults so liberally because this is a beta version of the app, whereas in the actual app I will have a database and API calls instead. I think it may have something to do with my actual project - I am getting true for session.reachable even when the Watch App is closed, which is concerning. I also am getting an error saying that the Watch Binary is not connected to the Watch Extension, which again is concerning. I may just have to start with a fresh project and set it up again. – emleeh Mar 08 '16 at 08:34
1

I had the same issue and fixed. I forgot to add WatchConnectivity framework in watch extension.

TK189
  • 1,490
  • 1
  • 13
  • 17