Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent behavior of openParentApplication in my WatchKit App

I'm developing an Apple Watch application that uses the openParentApplication:reply: method to communicate with its parent app.

The parent app communicates with a web service and sends back the data it gets to the watch extension by means of calling the reply method with a NSDictionary containing the data.

The app works perfectly when the parent app is open in the foreground or background. But if I open the parent app and then terminate it using the task switcher, the first time the watch extension makes a call to openParentApplication:replyInfo:, it gets the following error and the parameter replyInfo comes in as nil.

UIApplicationDelegate in the iPhone App never called reply()

But every single openParentApplication:replyInfo: call the extension makes after that gets a proper response.

I checked and found out that the first time the watch extension makes the call, the handleWatchKitExtensionRequest:reply: is never get called on the parent app.

What could be the possible reason for this?

I'm performing all operations in the handleWatchKitExtensionRequest:reply: in a background task, as suggested in the docs. Here's some of my code: Code from my extension:

NSDictionary *params = @{@"requestCode": @(RequestGetLoggedIn)};

[WKInterfaceController openParentApplication:params reply:^(NSDictionary *replyInfo, NSError *error) {
    // Do something with the result
}];

Code from the parent app:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
    }];

    NSNumber* requestCode = userInfo[@"requestCode"];

    // Perform some request and then call reply()

    // End the background task
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
    });
}

Edit 1: The problem occurs both on the Simulator and on a real Apple Watch.

like image 382
Cihan Tek Avatar asked Jul 03 '15 15:07

Cihan Tek


1 Answers

It looks like there's a bug in iOS 8.4.

I've added NSLog's to the beginning of application:didFinishLaunchingWithOptions: and handleWatchKitExtensionRequest:reply:, performed the actions that lead to the problem and then checked the device log and got this:

--- Notice>: (Warn ) WatchKit: <SPCompanionAppServer.m __91-[SPCompanionAppServer launchCompanionAppForGizmoAppWithIdentifier:withUserInfoData:reply:]_block_invoke_2:1450> Got BSActionErrorCodeResponseNotPossible for com.xyz.xyz.watchkitapp. This will translate to WatchKitApplicationDelegateWatchKitRequestReplyNotCalledError

... Irrelevant stuff

--- WatchKit Extension[1686] <Warning>: __59-[InformationController getNotificationListIncremental:]_block_invoke (null)
**--- <Warning>: MY LOG: Application did launch with parameters (null)**

This log shows that application:didFinishLaunchingWithOptions: gets called AFTER the OS gives an error about not getting a response from the parent app. How's the app gonna give a response if it's not getting launched first?

I've temporarily solved the problem by calling the openParentApplication:reply: method again when this problem occurs.

The way I've implemented the retry once behaviour is by creating a method that wraps the call and using that one instead the original method. I added this as a class method to a utility class, but it can be a global function as well.

+ (void)openParentApplication:(NSDictionary*)params reply:(void(^)(NSDictionary *replyInfo, NSError *error))reply
{
    [WKInterfaceController openParentApplication:params reply:^(NSDictionary *replyInfo, NSError *error) {
        if (error.domain == WatchKitErrorDomain && error.code == WatchKitApplicationDelegateWatchKitRequestReplyNotCalledError)
        {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [WKInterfaceController openParentApplication:params reply:^(NSDictionary *replyInfo, NSError *error) {
                    reply(replyInfo, error);
                }];
            });
        }
        else
            reply(replyInfo, error);
    }];
}
like image 163
Cihan Tek Avatar answered Oct 01 '22 16:10

Cihan Tek