12

My app needs to track the users location in the background but it is failing to send a 'get' request. The http request gets sent immediately when the app comes to the foreground. I am using RestKit for all my network requests and I followed this tutorial to setup my background locations service. In my applicationDidEnterBackground

-(void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgLocationManager = [[CLLocationManager alloc] init];
    self.bgLocationManager.delegate = self;
    [self.bgLocationManager startMonitoringSignificantLocationChanges];
    NSLog(@"Entered Background");
}

and I stopMonitoringSignificantLocationChange in my applicationDidBecomeActive delegate

This is my locationManager delegate where I accept the new updated location and send to my server

-(void) locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation
{
    NSLog(@"I am in the background");
    bgTask = [[UIApplication sharedApplication]
                beginBackgroundTaskWithExpirationHandler:
                ^{
                      [[UIApplication sharedApplication] endBackgroundTask:bgTask];
                 }];
                 // ANY CODE WE PUT HERE IS OUR BACKGROUND TASK

    NSString *currentLatitude = [[NSString alloc]
                                  initWithFormat:@"%g",
                                  newLocation.coordinate.latitude];
    NSString *currentLongitude = [[NSString alloc]
                                   initWithFormat:@"%g",
                                   newLocation.coordinate.longitude];
    NSString *webToken = [[NSUserDefaults standardUserDefaults] stringForKey:@"userWebToken"];
    NSLog(@"I am in the bgTask, my lat %@", currentLatitude);

    NSDictionary *queryParams;
    queryParams = [NSDictionary dictionaryWithObjectsAndKeys:webToken, @"auth_token",  currentLongitude, @"lng", currentLatitude, @"lat", nil];
    RKRequest* request = [[RKClient sharedClient] post:@"/api/locations/background_update" params:queryParams delegate:self];
    //default is RKRequestBackgroundPolicyNone
    request.backgroundPolicy = RKRequestBackgroundPolicyContinue;

    // AFTER ALL THE UPDATES, close the task

    if (bgTask != UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }
}

The network requests works as planned but it will not get called in the background. Is there any additional steps I need? In my info.plist I have the Required Background modes key and location-services as the value.

EDIT

I also referred to this past SO answer. I ran some tests with putting logs throughout the didUpdateToLocation call and they were all called but the 'get' request was not sent. Instead when I finally launch the app to the foreground it sent all the built of network requests (over 10).

EDIT (2) I added RKRequestBackgroundPolicyContinue to my request but it did not change my results. (As you can see here in the background upload/download for restkit). I see Restkit initialize the host but fails to send the request until the app becomes active.

ANSWER

RestKit must be doing something that is prohibited in the background. Using an NSURLRequest works perfectly.

NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com/api/locations/background_update"]];
[urlRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[urlRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[urlRequest setHTTPMethod:@"POST"];
[urlRequest setHTTPBody:jsonData];

NSHTTPURLResponse  *response = nil;
[NSURLConnection sendSynchronousRequest:urlRequest
                      returningResponse:&response
                                  error:&error];

It is fine to use a synchronous request since there is no UI to disrupt with background tasks

Community
  • 1
  • 1
Kyle C
  • 4,077
  • 2
  • 31
  • 34
  • Have your try replacing your restKit calls with a stock synchronous NSURLConnection? – dklt Sep 20 '12 at 02:29
  • i concur with dklt's question, because it seems likely that the call to `[[RKClient sharedClient] post:params:delegate:]` is causing something to happen that may be on a GCD-queue behind the scenes that is not covered by your backgroundTask bracketing … and that synchronous calls to get your data may help such that the requests are sent while in the background task. yes, the RKRequestBackgroundPolicyContinue may be meant to take care of this, but you're not actually setting it until after the call to post:params:delegate: , and by that point, it may be too late. – john.k.doe Sep 20 '12 at 22:53
  • @dklt I was able to get it working with a NSURLConnection request, thanks for the suggestion. If you put an answer in I will accept it. – Kyle C Sep 21 '12 at 15:32

3 Answers3

3

Re-creating original suggestion as an answer

Have your try replacing your restKit calls with a stock synchronous NSURLConnection? – dklt Sep 20

dklt
  • 1,703
  • 1
  • 12
  • 12
2

I'm using exactly the same code as you and it works for me in RestKit. The only way I could make it work is ny creating a synchronous request (it doesn't make a lot of sense to do it asynchronously in this context anyway!). Please check this code and let us know if it works:

// REMEMBER. We are running in the background if this is being executed.
// We can't assume normal network access.
// bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier

// Note that the expiration handler block simply ends the task. It is important that we always
// end tasks that we have started.

_bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:
           ^{
               [[UIApplication sharedApplication] endBackgroundTask:_bgTask];
           }];

// ANY CODE WE PUT HERE IS OUR BACKGROUND TASK

// For example, I can do a series of SYNCHRONOUS network methods (we're in the background, there is
// no UI to block so synchronous is the correct approach here).

NSNumber *latNumber = [NSNumber numberWithDouble:location.coordinate.latitude];
NSNumber *lngNumber = [NSNumber numberWithDouble:location.coordinate.longitude];
NSNumber *accuracyNumber = [NSNumber numberWithDouble:location.horizontalAccuracy];
NSDictionary *params = [NSDictionary dictionaryWithKeysAndObjects:@"lat",latNumber,@"lng",lngNumber,@"accuracy",accuracyNumber, nil];
RKURL *URL = [RKURL URLWithBaseURL:[NSURL URLWithString:SERVER_URL] resourcePath:@"/user/location/update" queryParameters:params];
RKRequest *request = [RKRequest requestWithURL:URL];
request.method = RKRequestMethodGET;
NSLog(@"Sending location to the server");
RKResponse *response = [request sendSynchronously];
if (response.isFailure)
    NSLog(@"Unable to send background location, failure: %@", response.failureErrorDescription);
else {
    NSError *error = nil;
    NSDictionary *parsedBody = [response parsedBody:&error];
    if (YES == [[parsedBody objectForKey:@"result"] boolValue]){
        NSLog(@"Background location sent to server");
    }
    else {
        //Something went bad
        NSLog(@"Failed to send background location");
    }
}
// AFTER ALL THE UPDATES, close the task

if (_bgTask != UIBackgroundTaskInvalid)
{
    [[UIApplication sharedApplication] endBackgroundTask:_bgTask];
    _bgTask = UIBackgroundTaskInvalid;
}

I'm almost sure the new thread spawned for your RKClient request is automatically killed after invoking it.

-1

When you're application is running in the background you can finish a HTTP request you started before you entered the background but you cannot initiate a new request. You can only initiate certain network operations while in the background (voip, newsstand).

Shizam
  • 9,627
  • 8
  • 51
  • 82
  • Thank you for your answer, I am still seeing conflicting info on this subject. Check out this past SO answer, http://stackoverflow.com/questions/5394880/can-iphone-app-woken-in-background-for-significant-location-change-do-network-ac – Kyle C Sep 17 '12 at 17:40
  • @KyleC What he is saying is, if you find your HTTP request (that you started while in the foreground) doesn't finish before your app is terminated then you should request extra time to finish the request using the beginBackgroundTaskWithExpirationHandler: method. This method allows extra time when first entering the background to complete any tasks you've started and want to finish but you can't start new HTTP requests while in this state, I've found this time to range anywhere from 5 seconds to over 10 minutes. – Shizam Sep 17 '12 at 18:45
  • 2
    I understand that part and I know the method beginBackgroundTaskWithExpirationHandler: is used for longer running tasks to finish when your app goes into the background. However, his answer suggests that your app is woken up in the background state by the significantLocation change and can use the beginBackgroundTaskWithExpirationHandler: to complete a http request all in the background. – Kyle C Sep 17 '12 at 19:03
  • Oh, cool! I didn't realize that was possible from that event. – Shizam Sep 17 '12 at 20:12