handleWatchKitExtensionRequest is not called - ios

I am trying to launch the parent ios app from watchkit app. I'm using url scheme to launch the app.But it seems like
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
is nevered called. It seems like watch app does launch the app in backgound. But the parent app does not handle the watchkit request. I tried my approach in a new project and it works perfectly. Is there any thing I need to pay attention?
I've already tried to Debug>Attach to process>myapp and put a breakpoint inside handleWatchKitExtensionRequest method to confirm if it is called and it isn't called.
Here is the progress, I call openParentApplication when a button is clicked in watch app.
#IBAction func viewOniPhoneAction() {
let userInfo: [NSObject : AnyObject] = [
"userID" : user.userID
]
WKInterfaceController.openParentApplication(userInfo, reply: { (userInfo : [NSObject : AnyObject]!, error : NSError!) -> Void in
})
}
Here is my app delegeate
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSDictionary *replyDict = #{#"response": #"done"};
reply(replyDict);
}
I tried reply() in handleWatchKitExtensionRequest but I got this error in reply block from watch app
Error Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x60800026e0c0 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}

I got it to work!!! Having the same issue....
Just increase the beginBackgroundTaskWithExpirationHandler time to a larger value if you still don't get the data!!! I used 2 secs previously but my network is too weak!!!
I call openParentApplication when a button is clicked in watch app:
[WKInterfaceController openParentApplication:loadDetailChatDataDictionary reply:^(NSDictionary *replyInfo, NSError *error) {
Here is my app delegate:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // increase the time to a larger value if you still don't get the data!!! I used 2 secs previously but my network is too weak!!!
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
// --------------------
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
NSString *value = userInfo[#"key"];
if ([value isEqualToString:#"loadRecentChatData"]) {
reply(#{#"recents":recents}); // Add your reply here
}

handleWatchKitRequest isn't called when you open the app via a URL scheme. It is only called in response to requests made in the WatchKit extension made using openParentApplication:reply:. That's why you aren't seeing it being executed.

You will need to wrap your reply in a background task to ensure your parent app has time to respond.
-
(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask __block = [app beginBackgroundTaskWithName:#"watchAppRequest" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//make your calls here to your tasks, when finished, send the reply then terminate the background task
//send reply back to watch
reply(replyInfo);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[app endBackgroundTask:bgTask];
bgTask=UIBackgroundTaskInvalid;
});
}

Related

handlewatchkitextensionrequest not calling Async call in suspended state

From Apple watch extension, handlewatchkitextensionrequest is not calling async call and returns nill data to apple watch, below are code please help me out.
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if ([[userInfo objectForKey:#"request"] isEqualToString:#"NearestFacility"]) {
[self NearestFacilityClicked];
if (self.ary_WatchKitNearestFacility.count>0) {
NSDictionary *response = #{#"response" : self.ary_WatchKitNearestFacility};
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];
});
}
-(IBAction)NearestFacilityClicked
{
// ASIFormdatarequest call
[request startAsynchronous];
}
In watch os 2 you need to use session for transferring the data between watch & iPhone.
First you need to initialise the WCSession in viewdidload()
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
then to send the data you can use
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
Then, on the receiving end, you'll need to implement session:didReceiveUserInfo: (Developer documentation). Note, according to Apple's "watchOS2 Transition Guide,"
To begin communication, both your Watch app and your iOS app must have an active WCSession object. Typically, each app creates, configures, and activates a session object at launch time and stores a reference to it in a central location. When you want to send data, you retrieve the session object and call its methods.

Send silent push notification to app, update location and send to server in background

I want to send a silent push notification to an application that is in background, then fetch the current user location and send it to a web service.
I implemented push notification methods and also those two:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSDate *fetchStart = [NSDate date];
[self sendLocationToServerWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
}
I've also created a method that will send the location to the server:
- (void)sendLocationToServerWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSDictionary *params = #{
#"UserId" : self.userId,
#"Latitude" : self.latitude,
#"Longitude" : self.longitude
}
ServerManager *manager = [ServerManager sharedManager];
[manager sendLocationToServerWithCompletion:^(BOOL success) {
if (success)
{
completionHandler(UIBackgroundFetchResultNewData);
}
else
{
completionHandler(UIBackgroundFetchResultFailed);
}
}];
}
I just can't understand how they all work together, will Apple approve that, is it even possible and where does the location background fetch goes into.
Thanks in advance.
Here's a brief sketch of what you can do to give you an idea. Its assuming there is a model class implemented as a singleton and there's some pseudo code.
// App delegate
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
completionHandler(UIBackgroundFetchResultNewData);
[[YourModel singleton] pushNotificationReceived: userInfo];
}
// Model
- (void) pushNotificationReceived:(NSDictionary *) userInfo
{
[self registerBackgroundTaskHandler];
get the location here, or start getting the location
[self sendLocationToServerWithCompletionHandler: your completion handler];
}
- (void) registerBackgroundTaskHandler
{
__block UIApplication *app = [UIApplication sharedApplication];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
DDLogInfo(#"BACKGROUND Background task expiration handler called");
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = 0;
}];
}
- (void) endBackgroundTask
{
if (self.backgroundTaskId)
{
UIApplication *app = [UIApplication sharedApplication];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = 0;
}
}
You'll need to get the location before you can send it. If you are just getting one location and you're using iOS9 you can use CLLocationManager:requestLocation: and you could fit this in relatively easily into where I've said "get the location here".
If you're not using iOS 9 (requestLocation is new with iOS 9) its a bit more complex.
How to use the location manager is a topic in itself and too much code to post here. You need to read and study all about using the location manger before you can incorporate it.
If you need a stream of location updates it gets more complex and where it says "or start getting the location" is a lot more involved then is implied in the pseudo code.
My recommendation, start with iOS9 and getting one instance of the location, then when thats working, add more functionality or iOS8 support if you need it.

iOS / Apple Watch: iPhone app network request callback blocks not triggered when app is in background

My Apple Watch app sends a message to the companion iPhone app. In the main app's handleWatchKitExtensionRequest, I send a request to the server:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
...
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
return;
}];
}
}
getPendingNotifications above is just a regular network GET request using AFNetworking.
It all works well when the app is active. Because this network request is used to populate the UI on my Apple Watch, I do not wish the main app to be active. However, when the main app on iPhone is in background, I can see the network request being sent out, but the withSuccessBlock or withFailureBlock callback blocks in the above code never gets triggered.
Can the phone app receive network request responses in background mode? If so, what am I doing wrong?
I have found a solution online that works for me, a post (http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply) by Brian Gilham.
And here's the code that works for me.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// There is a chance that the iOS app gets killed if it's in the background
// before it has a chance to reply to Apple Watch.
// The solution is to have the app respond to the request asap, then complete other tasks.
// The following code begins – and ends, after two seconds – an empty background task right at the beginning of this delegate method
// Then we kick off a background task for the real work
// For more details see http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[self handleWatchKitGetPendingChallengeRequest:reply];
}
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
- (void)handleWatchKitGetPendingChallengeRequest:(void (^)(NSDictionary *))reply {
...
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
reply(nil);
return;
}];
}
Try to send the request as a synchronous request.
I guess that your request is asynchronous request (as it should be in regular cases). The problem that in background mode, the device will lunch your app in background thread, and you created a new thread for the request.

The UIApplicationDelegate in the iPhone App never called reply

I am trying to launch my iPhone app from watch simulator using the below code :
WKInterfaceController subclass
[WKInterfaceController openParentApplication:[NSDictionary dictionaryWithObject:#"red" forKey:#"color"] reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(#"replyInfo %#",replyInfo);
NSLog(#"Error: %#",error);
}];
AppDelegate.m
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(#"appdelegate handleWatchKitExtensionRequest");
NSLog(#"NSDictionary: %#",userInfo);
NSLog(#"replyInfo: %#",replyInfo);
}
The error I am getting is :
Error: Error Domain=com.apple.watchkit.errors Code=2 "The
UIApplicationDelegate in the iPhone App never called reply() in
-[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]"
UserInfo=0x7f8603227730 {NSLocalizedDescription=The
UIApplicationDelegate in the iPhone App never called reply() in
-[UIApplicationDelegate application:handleWatchKitExtensionRequest: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.
Aside from just not calling the reply block, this can happen for at least a couple reasons:
Your iPhone app crashed while it was processing the request and therefore was never able to call the reply block. Check that you are not accidentally putting nil into an NSMutableDictionary, as that will cause a crash.
You are trying to put something that can't be serialized into a plist file into the replyInfo dictionary (hat tip to #duncan-babbage). If you need to pass an NSAttributedString or your custom object, make sure it conforms to NSCoding and do this:
On the phone side build your reply dictionary:
NSMutableDictionary *reply = [NSMutableDictionary new];
MyCustomObject *myObject = <something you need to send>;
reply[#"myKey"] = [NSKeyedArchiver archivedDataWithRootObject: myObject];
NSAttributedString *myString = <some attributed string>;
reply[#"otherKey"] = [NSKeyedArchiver archivedDataWithRootObject: myString];
And unpack it back on the watch side:
NSData *objectData = replyInfo[#"myKey"];
MyCustomObject *myObject = [NSKeyedUnarchiver unarchiveObjectWithData: objectData];
NSData *stringData = replyInfo[#"otherKey"];
NSAttributedString *myString = [NSKeyedUnarchiver unarchiveObjectWithData: stringData];
I would like to add that it is important to start a background task in handleWatchKitExtensionRequest as specified in the documentation. This ensures that the main app on the iPhone is not suspended before it can send its reply. (Not initiating a background task does not cause a problem in the simulator or when the iPhone app is active. However, it causes a problem when the iPhone app is inactive.)
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];
} );
}

Start background task after receiving push in Suspended mode

It may seem that this question was asked several times, but I'm facing a weird problem.
I have server configured to send push notification with content-available = 1 flag.
I have configured my app to work in background Background Modes on for Location Update, Background fetch and Remote Notifications.
Also I have implemented all necessary code to receive push notifications in background and to start background task.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
__block UIBackgroundTaskIdentifier bg_task = background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: bg_task];
bg_task = UIBackgroundTaskInvalid;
}];
//### background task starts
[self updateLocationToServer];
//#### background task ends
completionHandler(UIBackgroundFetchResultNewData);
}
- (void)updateLocationToServer{
[locationManager updateLocationWithCompletionHandler:^(CLLocation *location, NSError *error, BOOL locationServicesDisabled) {
if (error)
{
// Handle error here
if (locationServicesDisabled) {
// Location services are disabled, you can ask the user to enable them for example
}
}
else
{
// Do whatever you want with the current user's location
NSString *deviceID = [userDefs objectForKey:#"deviceID"];
isConnected = [[userDefs objectForKey:#"connected"] boolValue];
if (isConnected) {
if (deviceID) {
[self sendLocation:deviceID];
}
}
localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = [NSString stringWithFormat:#"Lat: %# Long:%#",[NSNumber numberWithFloat:location.coordinate.latitude],[NSNumber numberWithFloat:location.coordinate.longitude]];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
NSLog(#"Lat: %# Long:%#",[NSNumber numberWithFloat:location.coordinate.latitude],[NSNumber numberWithFloat:location.coordinate.longitude]);
//Clean up code. Tell the system that we are done.
[[UIApplication sharedApplication] endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;}}];}
EDIT: Added code where I end background task. background_task variable is global.
The app receives push in background normally until it goes to suspended mode. The problem is that, after background task ends, and the app goes to suspended mode it does not run the code again when it receives push notification but didReceiveRemoteNotification: fetchCompletionHandler: does not get called. But when I open the app and exit with home button it will work again within "that" 3 minutes until it goes to suspended mode.

Resources