Start background task after receiving push in Suspended mode - ios

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.

Related

Concurrent beginBackgroundTaskWithName on push notifications

I want to start and background task on receipt of a push notification. So far I have the following:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSString* taskName = [NSString stringWithFormat:#"PushNotificationTask-%#", [[NSUUID UUID] UUIDString]];
NSLog(#"Starting bg-task for push notification %#", taskName);
self.pnBgTask = [application beginBackgroundTaskWithName:taskName expirationHandler:^{
NSLog(#"Expiring bg-task for push notification %#", taskName);
[application endBackgroundTask:self.pnBgTask];
self.pnBgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do work here
NSLog(#"Ending bg-task for push notification %#", taskName);
[application endBackgroundTask:self.pnBgTask];
self.pnBgTask = UIBackgroundTaskInvalid;
});
In the situation where a push notification arrives before the background task of the previous one has finished, this code fails because one task will overwrite the other.
Can someone suggest a good pattern to resolve this problem?

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.

iOS8 UILocalNotification not working

I am using following code for local notification. But It is not working. Location is successfully being updated and it get into these methods but notification is not being fired. Any idea?:
NOTE: It is working when the app is in background but not working when the app is closed.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
}
[[UIApplication sharedApplication] cancelAllLocalNotifications];
// Override point for customization after application launch.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
return YES;
}
-(void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"You are checked in";
notification.soundName = #"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
}
You should keep in mind that notifications present only when your app in background, not at the foreground. If your are in foreground implement - application:didReceiveLocalNotification: of the AppDelegate and handle notification manually by yourself.
UPD
If your app is not running even in background, your code will not be executed. Look for Background modes (Tracking the User’s Location section) for possible solutions in order to ask system launch your app by events even currently it is not in the memory
My case was the notification was disabled from the
Settings -> Notifications -> YOUR_APP -> Allow Notifications
Local notification will work even your application removed from the background. But in your case, you are listening to the location manager event and triggering the local notification inside the delegate method. Your location manager event will not get triggered once you killed the application. So your local notification will not get triggered at all.
Wen app is closed applicationDidEnterBackground was called so put this background task code. and class the local notification.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// 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;
});
//// just u call the method want ever u want example
[self notification];
}
- (void)notification
{
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"You are checked in";
notification.soundName = #"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
I think this is helpful to u.
When your app gets killed, beacon monitoring will still relaunch it — that's the great thing about beacon monitoring!
All you need to do is re-start the monitoring in the application:didFinishLaunchingWithOptions: method. If you do that, the didEnterRegion event which resulted in launch of the app will immediately get delivered to the delegate, which should trigger the notification.
Estimote has a more detailed guide:
Launching notifications in iOS when the app is killed. It uses ESTBeaconManager, ESTBeaconManagerDelegate and ESTBeaconRegion classes from the Estimote SDK, but you can simply replace these with regular Core Location classes for the same effect.

End background task and wait until app becomes active again - BLE processing data

my app downloads a bunch of data through BLE from a peripheral. If I lock the screen my app gets moved into the background and it starts an background task. The download finishes fine but if the processing (which takes rather long because it is a lot of data) begins the app craches because it cant connect to the database.
I want to stop the execution at that point and wait for the app to become active again, but somehow I cant achieve this. I think I need some kind of semaphore to wait for the app to become active.
Here my code so far:
- (void)viewDidLoad
{
//Some other code
//initialize flag
isInBackgroud = NO;
// check if app is in the background
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
// check if app is in the foreground
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)appDidEnterBackground {
NSLog(#"appDidEnterBackground");
isInBackground = YES;
UIApplication *app = [UIApplication sharedApplication];
NSLog(#"remaining Time: %f", [app backgroundTimeRemaining]);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"expirationHandler");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
- (void)appDidEnterForeground {
NSLog(#"appDidEnterForeground");
isInBackground = NO;
if (bgTask != UIBackgroundTaskInvalid) {
UIApplication *app = [UIApplication sharedApplication];
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
//BLE connection and reading data via notification
//when finished [self processData] is called.
- (void)processData {
if (isInBackground) {
//set reminder
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate date];
localNotification.alertBody = [NSString stringWithFormat:#"Data was downloaded, return to the application to proceed processing your data."];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
UIApplication *app = [UIApplication sharedApplication];
//end background task
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
//wait for application to become active again
while (isInBackground) {
NSLog(#"isInBackground");
NSLog(#"remaining Time: %f", [app backgroundTimeRemaining]);
sleep(1);
}
//process data
}
So I have notices that if I call [app endBackgroundTask:bgTask]; the app just continues running but then crashes when I want connect to my database. Thats why I added the while(isInBackground) loop. I know that this is not good practice because it actively wastes CPU time while doing noting. I should use a semaphore at that point, but I cant figure out how to do it.
Because I'm actively waining in that loop, appDidEnterForegronund is never called and the loop runs forever.
You shouldn't be looping because your app only gets so long to process before it's stopped by iOS. Instead, when your app enters the background, set a state variable that it's in the background. Do the same for the foreground.
Only update the database if you're in the foreground, otherwise, set a state variable that tells your app that you've finished downloading, but still need to process the data. Store the data if you need to.
Then, when your app is relaunched, check the state of that variable and do the processing.
Instead of sitting in a loop waiting for some state to change, set variables, and use event-driven programming.

iOS 8 Silent Push Notification doesn't fire didReceiveRemoteNotification method when application not connected to xcode

I have seen too many questions about the silent push notification does not work if the device is not connected to xcode, but I could not find the real answer.
I'm using Silent APN to start a process in background and then fire a local Push notification
Server sends this info:
"_metadata" = {
bagde = 1;
pushText = "semeone has sent you a message!!";
sender = "semeone";
};
aps = {
"content-available" = 1;
};
And _metadata is customized info to fire the local notification, I did not included badge, pushText.. in aps because I it is a silent push notification.
Client should get the info in didReceiveRemoteNotification,
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if(application.applicationState != UIApplicationStateActive ){
if([userInfo[#"aps"][#"content-available"] intValue]== 1) //it's the silent notification
{
//start a background task
UIBackgroundTaskIdentifier preLoadPNTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task to start a process ");
}];
//end completionHandler regarding to fetchCompletionHandler
completionHandler(UIBackgroundFetchResultNewData);
// doing my process...... and fire a local notification
if(preLoadPNTask){
NSLog(#"End Background task ");
[[UIApplication sharedApplication] endBackgroundTask:preLoadPNTask];
preLoadPNTask = 0;
}
return;
}
else
{
NSLog(#"didReceiveRemoteNotification it's NOT the silent notification ");
completionHandler(UIBackgroundFetchResultNoData);
return;
}
}
else {
if(preLoadPNTask){
NSLog(#"End Background task ");
[[UIApplication sharedApplication] endBackgroundTask:preLoadPNTask];
preLoadPNTask = 0;
}
completionHandler(UIBackgroundFetchResultNewData);
}
}
It works perfectly fine when the device is connecting to xcode, but when it doesn't, the didReceiveRemoteNotification doesn't start :(
Any ideas?
Thank you in advance!!
What I end up is a cable USB was cause me some issues apparently every time that I plugged in the iphone device said that "this accessory may not be supported" but it continue working normally , so I replace for a new one, but that not solve my issue, however can be part of this. so I looked in the code, and I did some changes, after receive 2 o more silent push notification preLoadPNTask (UIBackgroundTaskIdentifier) was creating many times so I added a validation before it start,
if(!preLoadPNTask){
//start a background task
UIBackgroundTaskIdentifier preLoadPNTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task to start a process ");
}];
}
I hope this help you
Regards
In ios 8, You need to the following steps to fire didReceiveRemoteNotification: method
Select project target goto Capabilities tab
Select 'Background modes' turn on.
It'll add a key (Required background modes)in your project info.plist
After these modifications, when you get a apple push notification and if the app is in background already then didReceiveRemoteNotification will be fired.
Probably because under iOS 8 you have to ask for push notifications in a different way. Try this:
-(void) registerForPushNotifications {
UIApplication* application=[UIApplication sharedApplication] ;
// Register for Push Notitications, if running iOS 8
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
} else {
// Register for Push Notifications before iOS 8
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
}
}

Resources