didReceiveApplicationContext not being called
Asked Answered
P

4

6

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];
}
Pronucleus answered 7/3, 2016 at 21:32 Comment(2)
There's no method named didReceiveApplicationWithContext. Post your actual code.Punke
@Punke updated original postPronucleus
M
25

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

Memo answered 20/12, 2017 at 23:21 Comment(1)
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).Nathalie
L
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);
}
Laniary answered 8/3, 2016 at 17:24 Comment(0)
M
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.

Magill answered 7/3, 2016 at 23:35 Comment(1)
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.Pronucleus
P
1

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

Patagium answered 18/6, 2016 at 23:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.