How to Correctly Use "Openparentapplication" and "Handlewatchkitextensionrequest" So That "Reply()" Is Called

How do I correctly use openParentApplication and handleWatchKitExtensionRequest so that reply() is called?

When the main app on the iPhone is not active, reply() may not be reached because the background task is killed by the OS before.

The solution is to explicitly start a background task in handleWatchKitExtensionRequest as specified in the documentation. If a background task is initiated like this, it can run up to 180 seconds. This ensures that the main app on the iPhone is not suspended before it can send its reply.

Code in the app delegate of the main app on iPhone:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];

if ( [[userInfo objectForKey:@"request"] isEqualToString:@"getData"] )
{
// get data
// ...
reply( data );
}

dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1 ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
} );
}

In case you need to asynchroneously fetch data, use the following approach to ensure that the method does not return immediately (without calling reply):

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;

watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];

NSMutableDictionary *response = [NSMutableDictionary dictionary];

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

[ClassObject getDataWithBlock:^(BOOL succeeded, NSError *error){

if (succeeded)
{
[response setObject:@"update succeded" forKey:@"updateKey"];
}
else
{
if (error)
{
[response setObject:[NSString stringWithFormat:@"update failed: %@", error.description] forKey:@"updateKey"];
}
else
{
[response setObject:@"update failed with no error" forKey:@"updateKey"];
}
}

reply(response);
dispatch_semaphore_signal(sema);
}];

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

dispatch_after(dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
});
}

handleWatchKitExtensionRequest not responding to openParentApplication in Watchkit Extension (Swift)

Ah, I think what is happening here is that your code is both correct, and working exactly as it should, and what you're interpreting here is a result of an overlap of two entirely understandable assumptions, that are actually not correct and have been leading you astray. So the good news is, your code is already working.

You say,

...which shows in the output that the function is being called...

If by that you mean that in the console you are seeing the message, openParentApplication called in button function, then here's what is going on:

This part of your code is a Swift Closure:

{(reply, error) -> Void in
println("openParentApplication called in button function")
}

When your WatchKit Extension calls WKInterfaceController.openParentApplication it passes to your parent iPhone app a dictionary (your testDict), which the iPhone application can use to return data to you—providing the data has been serialized. It also returns back to you the closure that you passed it. This enables your WatchKit Extension to run code that it has itself defined, at the later point when the reply has been received. You can include in this closure use of both the returned data in the testDict and also other variables that were locally accessible at the time openParentApplication was called. Your WatchKit Extension automatically executes the code in the closure when it is received back.

So when you see openParentApplication called in button function, this indicates that the reply from the iPhone application has been received, and the closure has been executed. Therefore, your WatchKit test code should really change the println statement to be:

WKInterfaceController.openParentApplication(testDict,
reply: {(reply, error) -> Void in
println("Reply to openParentApplication received from iPhone app")
})

Now, the reason why you quite understandably didn't realise the code was executing correctly was because you were expecting to see rejected in the console that this code had been executed in your iPhone app:

println("we made it!")

However, Xcode does not support attaching to two processes simultaneously. Therefore, when you are attached to your WatchKit app, you will not see any log messages of your iPhone app. Your iPhone app also will not respond to breakpoints when it is not the attached process. Both of these are true whether it is running in the background (woken by openParentApplication) or running in the foreground (if you launch it manually in the simulator after the WatchKit app has been run. You can see the effects of the iPhone app activity, but cannot directly introspect it while you are attached to the WatchKit app.

So firstly, your code is working correctly. You can move past your test code! And in relation to introspecting the workings in the iPhone side when it is responding to your WatchKit app, there is a partial solution. Launch the WatchKit app from the simulator, and once it is running, in Xcode activate the menu option Debug > Attach to process... and select your iPhone app process under Likely targets at the top. Now you will see your iPhone app console messages and your iPhone app will respond to breakpoints—but of course you will no longer see these from the WatchKit app side. You continue to be able to interact with both apps in the simulators, and can swap back and forth between which one you are attached to during execution.

WatchKit return reply() inside a block in handleWatchKitExtensionRequest:

To make sure that your asynchronous data fetch does not return immediately (without calling reply), you may try the following:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;

watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];

NSMutableDictionary *response = [NSMutableDictionary dictionary];

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

[ClassObject getDataWithBlock:^(BOOL succeeded, NSError *error){

if (succeeded)
{
[response setObject:@"update succeded" forKey:@"updateKey"];
reply(response);

}
else
{
if (error)
{
[response setObject:[NSString stringWithFormat:@"update failed: %@", error.description] forKey:@"updateKey"];

dispatch_semaphore_signal(sema);

reply(response);
}
else
{
[response setObject:@"update failed with no error" forKey:@"updateKey"];

dispatch_semaphore_signal(sema);

reply(response);
}
}
}];

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

dispatch_after(dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
});
}

Apple Watch, WatchKit Extension and main application

To communicate to the containing iPhone app you can use

(BOOL)openParentApplication:(NSDictionary *)userInfo
reply:(void (^)(NSDictionary *replyInfo,
NSError *error))reply

In your WKInterfaceController

From the Apple Docs

Use this method to communicate with your containing iOS app. Calling
the method causes iOS to launch the app in the background (as needed)
and call the application:handleWatchKitExtensionRequest:reply: method
of its app delegate. That method has the following signature:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo
reply:(void(^)(NSDictionary *replyInfo))reply

The app delegate receives the dictionary you pass into the userInfo
parameter and uses it to process whatever request you made. If it
provides a reply, WatchKit executes the block you provided in the
reply parameter of this method.

The UIApplicationDelegate in the iPhone App never called reply

You need to call the reply block, even if you return nil. The following will resolve your error:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(@"appdelegate handleWatchKitExtensionRequest");
NSLog(@"NSDictionary: %@",userInfo);
NSLog(@"replyInfo: %@",replyInfo);
reply(nil);
}

See the Apple documentation for further information. You can also return an NSDictionary reply(myNSDictionary); with whatever information it would be useful to return to your Watchkit extension, although the dictionary can only contain information that can be serializable to a property list file, so for instance you can pass strings but you can't just pass a dictionary containing references to instances of your custom classes without packaging them up as NSData first.

Getting WebService response from Block to WatchKit App

So finally I got my answer by doing Sync WS request,

func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {

getCurrentDataInfo(userInfo, reply: reply)

}

func getCurrentDataInfo(userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) ->Void)!){

var data = parseJSON(getJSON("https://My_JSON_URL"))

println(data)

reply(["myData":data])

}

func getJSON(urlToRequest: String) -> NSData{
return NSData(contentsOfURL: NSURL(string: urlToRequest)!)!
}

func parseJSON(inputData: NSData) -> NSDictionary{
var error: NSError?
var boardsDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(inputData, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary

return boardsDictionary
}


Related Topics



Leave a reply



Submit