HKBackgroundSync Launching app and draining Battery

We have enable HK background sync and it works great, every time there is a new object in HK, the app gets the object and we post the object to the server etc.

The problem is that every time the HKObserverQuery(sampleType: sampleType, predicate: nil) is called due to a new HK object, we are also getting the didFinishWithOptions method in the app delegate to be called.

The problem here is that when didFinishWithOptions is called we fetch user details, fetch a bunch of data from the server and make lots of tasks. Is there a way to know if the app is being launched because of a HK background sync task or because the user launched the app? Or best practice recommendation to not start every single task in the app we trigger on regular launches just for a HK import?

Thank you

Accepted Answer

This is an idea we are testing, but so far it looks like it works so I wanted to share. We mostly use "UIApplication.shared.applicationState"

Implementation

1.) crate an Enum to managed the states

    //State where the app runs a background tasks and we don't need to activate the user
    case unknown
    //State where app is open while some background tasks is active so we need to create the user
    case needsToEnableUser
    //State where the user has being activated
    case userEnabled
  }

2.) inside the didFinishLaunchingWithOptions in the AppDelegate, I add this code

 if UIApplication.shared.applicationState != .background {
      userAppState = .needsToEnableUser
    }

3.) a few lines below inside the same didFinishLaunchingWithOptions I call this method I crated handleUserSignIn (this is where we do all the heavy stuff we only want to happen if a user in fact is opening the app instead of every single HK background Sync) so Our code looks something like this

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

   if UIApplication.shared.applicationState != .background {
      userAppState = .needsToEnableUser
    }

    handleUserSignIn()
}

4.) implement the method and and update the state

 func handleUserSignIn() {
    if userAppState == .needsToEnableUser {
      userAppState = .userEnabled

       //Do here all the User fetching server requesting you need!!
    }
  }

5.) Handle Special Case: If for example you unblock your phone and some process triggers the background launch, which fires the didFinishLaunchingWithOptions chances are that the ApplicationState is inactive and then when the user opens the app during that state since we haven't call our handleUserSignIn method, user finds a black screen or whatever default screen you have in your app that is not loading anything. So let's be sure we cover all the cases

using the applicationDidBecomeActive we know the user has open the app so we want to be sure we load all the user stuff if we haven't already done so

 func applicationDidBecomeActive(_ application: UIApplication) {
     
    if userAppState == .unknown {
      userAppState = .needsToEnableUser
      handleUserSignIn()
    }
}

Testing Special Case

  • in your app go to the Edit Scheme -> Options
  • select the option that says Background Fetch _ Launch due to a background fetch event
  • Hit run,
  • when you see console logs (app is running now)
  • Go to the simulator and hit your app icon

-> At this point everything should work as usual and the user data should be available like always

Additional Notes

Be aware that when a background task calls didFinishLaunchingWithOptions at some point not matter what you have to call the code you use to do your Background Fetch HKObserverQuery(sampleType: sampleType, predicate: nil).....

What you do with this code is up to you, but since you are not powering up your full user, be sure you have init whatever it is you need for this particular task to run to completion, Example post to the server or save it locally.

I hope this helps.

HKBackgroundSync Launching app and draining Battery
 
 
Q