Load data asynchronously after notification on apple watch - ios

I'm receiving a notification that has, among other things, an ID in its payload. This ID is simply there so I can fetch the actual object on the server.
Right now, I only communicate using the AppDelegate HandleWatchKitExtensionRequest, and the .OpenParentApplication from the Watchkit.
Using that, if I don't make any webservice calls, it's all fine. Once I try to call the server, I get an error saying that the parent App never calls reply().
So obviously I'm thinking that the async calls makes it fail.
What's the best way to achieve this? I have a feeling I'm doing this wrong but I have no other real solution from what I've found online.
Right now it looks like this
[Watch] Remote Notification received
[Watch] Open parent application with ID in dictionary
[Parent] Call webservice (Fails here)
[Parent] Send results to the watch (some strings and an image)
Note : I'm using Xamarin, but any Obj-C/Swift code will suffice, and I need to be compatible with WatchOS 1, therefore I can't really use WCSession/WatchConnectivity framework, can I?

been in very same situation, and hope following will help you. So, when watchkit app calls the main app, begin background task, load the things you are loading, and end the bg task. Like this:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
//load the things here.
reply(#{#"hehe":#"hehe123"});
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 10. ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
} );
}
Hope this was useful.

Related

backgroundTask not finishing in iOS

I have the following code which makes a call to the server, before the app is about to exit. My problem is, that the code sometimes works, sometimes not. Mostly not. Here is the code:
//Set the user as in-active if app is force closed
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(#"called");
bgTask = [application beginBackgroundTaskWithName:#"setInActive" expirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"disp called");
//If the app is about to exit make the user inactive
[[APIManager sharedInstance] setInActiveOnCompletion:^(BOOL finished){
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
});
}
The first NSLog gets called everytime. However the second one does not. It seems as if the app does not even go into the dispatch_async method.
EDIT: So basically all I need to do is tell the server that a user has force quit the app, while that user was signed in. How could I do this?
Any ideas?
applicationWillTerminate is the last method that get called before app is being terminated!! So, you can't do anything after that, if you want to perform something in background then begin that task when you performing your operation, you can't begin background task from applicationWillTerminate because your app will not be in background after this method call!!
When your application is in the background, it is only allowed very little amount of processing, therefore using dispatch_async will not buy you much extra time. Also, Apple does not want that.

Request from Apple Watch takes an absurd amount of time

In my apple watch application I am making a request to location services and to a web service. Performing both these tasks on the iPhone takes roughly 2 seconds, at most. However, when I am making the same requests from the Apple watch it takes more than 10 seconds before I get a reply...unless the iPhone application is already running in which case the request takes about 2 seconds.
I figure there is something wrong with my background task in application:handleWatchKitExtensionRequest:reply: but I cannot figure out what. All hints are appreciated.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier identifier = UIBackgroundTaskInvalid;
dispatch_block_t endBlock = ^ {
if (identifier != UIBackgroundTaskInvalid) {
[application endBackgroundTask:identifier];
}
identifier = UIBackgroundTaskInvalid;
};
identifier = [application beginBackgroundTaskWithExpirationHandler:endBlock];
reply = ^(NSDictionary *replyInfo) {
reply(replyInfo);
endBlock();
};
// Call location services with completion block ^{
// Use the location and make request to web service
// with completion ^{
// reply(#{#"info" : #"here be dragons"});
// };
//};
}
In order to save power, the Apple watch limits the update frequency for location services (a major power hog). When you open the iPhone application, you prioritize the task you're running, and the Apple watch will update its location more often.
You can try to force the application to update faster by sending a "wake up" ping: send an empty/dummy call to the application and reply with a dummy reply. This should wake up the connection for a while, and your second call might be processed faster, although I am unsure of whether this "wake up" ping will prioritize location services.
Last thing, IMHO 10 seconds isn't really an "absurd" amount of time.

Perform NSLog when App is Sent to Background

I have a seemingly simple task that I am trying to do. When a user sends my application to the background, I would like to conduct an NSLog to say "app sent to background". I am trying to work with the NSNotificationCenter like What's the best way to detect when the app is entering the background for my view?, but I am unable to get it to work.
Is it not possible to perform an action right when the user sends the app to the background? Any help would be great!
Thanks!
You may use NSLog in method of
- (void)applicationDidEnterBackground:(UIApplication *)application
Your AppDelegate.m file.
Where you can send NSNotification for any task. You can not perform any task when app is in background.
You need to implement the following method in AppDelegate:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"app sent to background");
}
Apple's documentation:
Your implementation of this method has approximately five seconds to
perform any tasks and return. If you need additional time to perform
any final tasks, you can request additional execution time from the
system by calling beginBackgroundTaskWithExpirationHandler:.
You can do a 10 min background activity in pre iOS7 like save some thing to your server but in order to wind up when background activity is about to be closed you need to use this in your - (void)applicationDidEnterBackground:(UIApplication *)application
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
For more detail look here
the summary is
your - (void)applicationDidEnterBackground:(UIApplication *)application method must return within 5 seconds or else your app will be purged.
you can how ever ask for more time using the above method the time allowed in practice is 10 mins

NSURLSession vs Background Fetch

Ok, so I was looking at the SimpleBackgroundFetch example project, and it uses the following in the App Delegate:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:someTimeInSeconds];
//^this code is in didFinishLaunchingWithOptions
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//do something + call completionHandler depending on new data / no data / fail
}
So, basically I assume, that I call my app's server here, to get some data.
But then I saw the NSURLSession docs, and it had methods like these
– downloadTaskWithURL:
and it said the following:
This API provides a rich set of delegate methods for supporting
authentication and gives your app the ability to perform background
downloads when your app is not running or, in iOS, while your app is
suspended.
So what's the difference between these two APIs? And what should I use if I want to download some data from my app's server every now and again?
I just wasn't sure about the difference between the two, so I just thought I should get my doubts clarified here. Go StackOverflow!
These are completely different things.
Background Fetch: System launches your app at some time (heuristics) and your job is to start downloading new content for user.
NSURLSession: Replacement for NSURLConnection, that allows the downloads to continue after the app is suspended.
The application delegate is for storing the completion handler so you can call it when your download is finished.
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
NSLog(#"Handle events for background url session");
self.backgroundSessionCompletionHandler = completionHandler;
}
and call the handler
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
WebAppDelegate *appDelegate = (WebAppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.backgroundSessionCompletionHandler) {
void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
appDelegate.backgroundSessionCompletionHandler = nil;
completionHandler();
}
NSLog(#"All tasks are finished");
}
NSURLSession:Allows to uploading and downloading in the background mode and suspend mode of application
Background Fetch:Happens according to volume of the data and duration of previous data transferring process.Only last for 30s.
So you confirm a background URLSession endowed with a delegate should be called, while a normal dataTask with block may not be?

If app sends a http request during termination, will the server receive it?

I want to know that if my iOS app is terminating, if I send a HTTP request informing the server that the user is going offline, will the server get this request?
My code is below:
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSMutableDictionary *para = [[NSMutableDictionary alloc]initWithCapacity:0];
[para setObject:[[NSUserDefaults standardUserDefaults] valueForKey:#"UserBindId"] forKey:#"unionId"];
[para setObject:#(0) forKey:#"OnlineState"];
[Post updateOnlineWithParameters:para withBlock:^(NSDictionary *commitResult, NSError *error) {
NSLog(#"applicationWillTerminate = %#", commitResult);
}];
}
See Executing Finite-Length Tasks for an example of how to have an app request a little extra time (max 3 minutes, which should be more than adequate for your purposes) when the app enters background mode.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"GoingOffline" expirationHandler:^{
// Well, we ran out of time before network request finished ... handle that however you want
// but tell the OS that we understand, and that the background task is done
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
NSDictionary *para = #{#"unionID" : [[NSUserDefaults standardUserDefaults] valueForKey:#"UserBindId"],
#"OnlineState" : #0};
[Post updateOnlineWithParameters:para withBlock:^(NSDictionary *commitResult, NSError *error) {
// when done, tell the OS that the background task is done
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
I tried to simplify your parameters a bit (no need for mutable dictionary ... you can use dictionary literal), so adjust that as suits your needs. But hopefully you can follow the basic idea here, if you want to do something when the app enters background, you must request permission for that.
You can't provide a failsafe user online/offline status capability from the client side, using http requests. As Rob said, if the app is terminated (force quit or crashes e.t.c.), theres nothing you can do. The way to provide this kind of feature is to use a web socket, which is a "constant" two way communication between the phone and server. This way on the server side, you can check whether the connection was lost, which indicates that the client has gone offline.
To create such a service yourself would take quite a long time. Luckily Firebase offer such software with a Backend as a Service. It's a godsend. This page describes how to manage offline and online status. It's as easy as pie.

Resources