Workout session causing app crashes with multiple paired Apple Watches - ios

Problem
I am developing an application that can track workout sessions for multiple paired Apple Watches (multiple watches paired to a single phone). I have implemented Watch Connectivity for multiple watches as per Apple Documentation, and that part of the application seems to be working okay. The application allows the user to initiate a workout session and pulls data from HealthKit so the user can view it in the future. I am having an issue with the application crashing (or at the very least, the handleActiveWorkoutRecovery method is called) after starting a workout following a pairing switch of the watches. As a sample timeline to what occurs:
Time (minutes)
Event
0
User starts workout session on Watch 1
5
User stops workout session on Watch 1
5
User switches pairing from Watch 1 to Watch 2
5
User starts workout session on Watch 2
6
The application "crashes" on Watch 2
I would include Crash logs, but the crash does not seem to show up anywhere. I have tried finding the logs through the organizer for TestFlight versions, and manually through sysdiagnose and the Devices and Simulators tab in XCode, but they just aren't anywhere, leaving me to hypothesize that this is not actually a crash (or at least that crash logs are not being generated).
What I have tried
I initially hypothesized there was some issue with the way I had implemented WCSession, but all three of the required methods session(_:activationDidCompleteWith:error:), sessionDidBecomeInactive(_:) and sessionDidDeactivate(_:) have been implemented in the code appropriately, as have all delegates. I also tried switching multiple times without starting a workout session to see if the application would crash, but it did not. I do not think this is the problem.
I also hypothesized that perhaps I was not ending the workout sessions appropriately and maybe the issue was something related to that. However, I am calling the appropriate .end() and .stop() methods for the workout session and HealthKit queries respectively. Additionally, I can restart the session on a single device after stopping it immediately, and it does not cause any issues. I do not think this is the problem either.
Current hypothesis
Currently, I hypothesize that the problem lies somewhere in starting the session too early after switching. So for example, while doing testing, I discovered that this timeline of events DOES NOT cause a crash
Time (minutes)
Event
0
User starts workout session on Watch 1
5
User stops workout session on Watch 1
5
User switches pairing from Watch 1 to Watch 2
10
User starts workout session on Watch 2
However, this timeline of events, DOES cause a crash.
Time (minutes)
Event
0
User starts workout session on Watch 1
5
User stops workout session on Watch 1
10
User switches pairing from Watch 1 to Watch 2
10
User starts workout session on Watch 2
11
The application "crashes" on Watch 2
Notice in the previous test I had waited 5 minutes before initiating the session on the newly paired watch, and in this one, I waited 5 minutes before switching the pairing, but start the workout session immediately after the switch. To be absolutely clear, I always wait for the switch to complete (a tick mark appears in the watch app on the phone when the switch is complete) before starting a tracking session.
Question
To me, it seems like there is something going on after switching for at least a few minutes wherein starting a session will cause a crash. Unfortunately, I do not know what that is, so am wondering if there is a chain of events that occur after switching the watch pairing which will cause the workout session to crash if it starts too early. Right now the only workaround I can think of is not allowing the user to initiate a workout session for a specific period of time after a watch switch, but I want to know if there is some sort of event that I can tie that functionality to, instead of just blocking the user for a period of time.

This isn't an answer, but: your use of HealthKit will not be relevant here, unless it's simply contributing to your control flow in a way that's causing some other mismatch. HealthKit workout sessions are fully independent on different devices, and so session control on one cannot affect sessions on another.

Related

What is the difference between an Apple HealthKit query running in the background vs. being enabled for background delivery?

I am reading through the documentation for the Apple HealthKit and am stuck on understanding the difference between a query being registered for background deliveries vs. running on the background. This excerpt from the explanation of anchored object queries seems to differentiate between the two:
Anchored object query. In addition to returning the current snapshot of modified data, an anchored object query can act as a long-running query. If enabled, it continues to run in the background, providing updates as matching samples are added to or removed from the store. Unlike the observer query, these updates include a list of items that have been added or removed; however, anchored object queries cannot be registered for background delivery.
Source: https://developer.apple.com/documentation/healthkit/reading_data_from_healthkit
I am confused about the difference between the two types of "backgrounds." Does the statement "continues to run in the background" really mean "when the app is in the foreground, this query will continue to run without needing to be re-called?"
Background Delivery works like background app refresh, or GPS significant location change events. It wakes up your app to allow you to act on some event. In this case whenever a health sample you are listening for, is added to the store, your app is woken up (once within a given interval) in order to be able to process the event.
Think of a running/cycling app, where a user is trying to tracking their progress on a long workout. The app won't remain open the whole time, but you can ask for the number of steps every X interval so that you can keep your UI updated, or in sync with a server.
More info on background delivery: https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery
The other reference to background, means that it continues to run in another thread, while the app is open. So if you query for heart rate, you can be notified every time a reading comes in, as opposed to just receiving 1 result and having to check every X seconds for a new one

Core Bluetooth - Performing Long-Term Actions in the Background

This is from the Core Bluetooth Background Processing for iOS Apps section found in official docs:
Performing Long-Term Actions in the Background
Some apps may need to use the Core Bluetooth framework to perform
long-term actions in the background. As an example, imagine you are
developing a home security app for an iOS device that communicates
with a door lock (equipped with Bluetooth low energy technology). The
app and the lock interact to automatically lock the door when the user
leaves home and unlock the door when the user returns—all while the
app is in the background. When the user leaves home, the iOS device
may eventually become out of range of the lock, causing the connection
to the lock to be lost. At this point, the app can simply call the
connectPeripheral:options: method of the CBCentralManager class, and
because connection requests do not time out, the iOS device will
reconnect when the user returns home.
Okay, we have an app which locks/unlocks doors as appropriate... So as pointed out, this works when app is in the background (most likely in the suspended mode). Now, lets move on (with quoting docs):
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not
be able to reconnect to the lock when the user returns home, and the
user may not be able to unlock the door. For apps like these, it is
critical to be able to continue using Core Bluetooth to perform
long-term actions, such as monitoring active and pending connections.
So, if the user was away from home for a few days, and app has been terminated by the iOS, we will have to implement state preservation and restoration, so that iOS relaunch the app when connection request is detected, and let the app to unlock the door. Related quotes:
In the case of the home security app described above, the system
would monitor the connection request, and re-relaunch the app to
handle the centralManager:didConnectPeripheral: delegate callback when
the user returned home and the connection request completed.
This all make sense, but pay attention to this part again:
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not be able to reconnect to the lock when the user returns home, and
the user may not be able to unlock the door. For apps like these, it
is critical to be able to continue using Core Bluetooth to perform
long-term actions...
Does this means, if the app is forcefully killed by the user at some moment while he was away from home, that this will work as well ? Means when user comes home, the door will unlock anyway, or he must manually relaunch the app to unlock the door?
I am asking this, because of how relaunch of terminated apps works. It is not the same when user kills the app, and when iOS kills the app which supports background execution:
Apps that support background execution may be relaunched by the system
to handle incoming events. If an app is terminated for any reason
other than the user force quitting it, the system launches the app
when one of the following events happens...
Source
So once again, if the user was away for a few days and he has closed the app by double tapping Home button and dragging up, will he be able to enter his home without manually relaunching the app?
No. If the app is forcefully killed by the user then it will not be woken up again. The only scenario where it will be woken up is if the app was terminated by iOS itself, which will happen sooner or later when the app has not been it the foreground for a while. It will also not be relaunched if the device is rebooted.
Having said that, from my experience with Core Bluetooth I have come to the conclusion that State Preservation is way too unreliable. I would believe that the use-case that you are trying to implement will not work well enough, which is ironic since it is exactly the use-case that Apple is promoting it their documentation.
For example, you will have issues with the following:
State restoration will only relaunch your app due to bluetooth related activity if the event originates from a peripheral accessory that you are communicating with, such as connect/disconnect events and characteristics notifications. For other events, most importantly general bluetooth-state-change events, your app will not be relaunched and notified of this. The reason why this is so bad is because any bluetooth-state-change events will cause all pending connections to be tossed, meaning that your pending connections to the door lock will be lost. However, since your app is not relaunched to be notified of this, then it effectively means that your application will still believe that the connections are still pending when in fact they are not. Since your application is terminated at this time, the only way for it to wake up again is by having the user manually launch it again (or alternatively “hack” other background modes for this purpose, which will NOT work very reliably either).
This scenario happens if the user toggles Flight Mode, toggles Bluetooth, power cycles the iOS device, or any other undefined reasons that many cause state changes… And it is very unlikely that a bluetooth-state-change will not happen if "... the user is away from home for a few days.".
This "issue" has been reported by me and others multiple times, but Apple does not appear to want to fix it for some reason.
Apart from this, many other issues exists as well, such as the XPC connection being interrupted at different times for no apparent reason. I have also noticed that the pending connection can go into “limbo” mode where the peripheral state gets set to Connecting, but in fact it will never connect unless you cycle the connection state. Etc, etc, ...
/A

HKWorkoutSession: Not getting Heart Rate when screen is turned off in watchOS 2

I am using iOS 9 beta 4 and watchOS 2 beta 4.
I can't seem to get any heart rate data when the watch screen turns black (locks). I will get a call to applicationWillResignActive and then the heart rate data just stops.
It seems that the sensor is deactivating after some time as well (not green anymore), when the screen locks.
Anyone else seeing this behavior? I can post my code for initing the WorkoutSession if anyone else getting heart rate data when the screen on the watch is locked.
Starting in watchOS 3.0 you can add the WKBackgroundModes entry with the workout-processingvalue to the extension's Info.plist.
From the docs:
Running in the Background Workout sessions can run in the background.
Background running grants an app the following abilities:
The app continues to run throughout the entire workout session, even
when the user lowers their wrist or interacts with a different app.
When the user raises their wrist, the app reappears, letting the user
quickly and easily check their current progress and performance.
The app can continue to access data from Apple Watch’s sensors in the
background, letting you keep the app up to date at all times. For
example, a running app can continue to track the user’s heart rate,
ensuring that the most recent heart rate data is displayed whenever
the user raises their wrist.
The watch app platform is designed to totally disable your code shortly after applicationWillResignActive(). However with an HKWorkoutSession active, the watch should record heart rate data to the HK store in the background (as long as another watch app doesn't start a new one and cancel yours).
This is roughly what I do to start heart rate collection:
func startCollectingHR(){
guard workoutSession == nil else{return} // Make sure it's not already started
// Also make sure you get the appropriate HealthKit permissions
workoutSession = HKWorkoutSession(activityType: HKWorkoutActivityType.CrossTraining, locationType: HKWorkoutSessionLocationType.Outdoor)
workoutSession!.delegate = self
healthStore.startWorkoutSession(workoutSession!)
print("HR collection started.")
}
Unfortunately, I haven't found any way to get heart rate to reliably live-stream anywhere outside the HealthKit store on the watch.
I noticed similar behaviour and found that the first workout session would run as intended, but subsequent ones would only run when the watch face was active. I solved the problem by resetting HKWorkoutSession once the session was completed.

Resume to last view controller from background

I've got an iOS application that requires the user to log in before using its features. It uses Bluetooth and location services in the background after logging in. The typical use of the application is:
User logs in -> transition to 'lobby' page
Selects 'begin logging data' -> transition to 'logging' page
Bluetooth and Location services run in the background
User locks screen and app runs in the background (this needs to last for 8-10 hours)
The way the app handles logins is via a session token which times out after 12 hours.
If I leave the application running overnight, however, upon resuming the application it starts up the log in view controller (a.k.a. the root view controller). I need the application to resume on the page it was closed on and can't seem to find any reference online as to how to do this.
I did find something about saving and resuming state but could get a definitive answer. Any help?
UIStateRestoration is the mechanism that Apple provides for your application to be archived when it is backgrounded. Even if the app is eventually terminated by the operating system, UIStateRestoration provides APIs to restore the state of your application to where the user left.
Resources:
State Preservation Programming Guide
If you didn't define a background task, the app will be killed once it stays over 20 minutes in background
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

why are my new flurry parameters not showing up?

I have been tracking flurry events in my iOS app and they show up in the dashboard. I tried to add a new parameter to an existing event, and then activated the event on my phone a bunch of times yesterday, but I still don't see any occurrences of the new parameters on that event in the dashboard.
Does flurry not allow you to add new parameters to an existing event?
We noticed this too a little bit ago, and emailed support#flurry.com about it. Here's what they said:
Typically, Flurry sessions never completely report back to Flurry until the next time the app is launched. So, be sure to launch the app one more time after you complete a testing session. Furthermore you should make sure you launch the same build of the app. If you make a new build between sessions you may be removing incomplete sessions before they get reported. If you are running tests via an emulator, please ensure that the home button is pressed before closing the app. Then you should relaunch the app one more time with the same build to ensure all session data is reported.
If the app pauses or moves to the background for more than 10 seconds, the NEXT time the app runs, Flurry agent will automatically create a new session and end the previous session. Otherwise, Flurry agent will continue the same session. This can be updated via the setSessionContinueSeconds method. If the app is terminated, a new session will be created when the app runs again.
When we receive the complete session data (after the app is relaunched) the event logs should update first within 10 minutes or so. Please allow about 6 hours for this to populate to the dashboard, parameters pie chart etc.
Basically, what we were doing is just booting up the app, doing the thing that would send an analytic event, then quitting the app or whatever. Which wasn't giving the flurry agent enough time to actually send the event. (turns out that, contrary to the log events, flurry wasn't sending the log event right then :/)
Hope that helps!
The Flurry SDK only communicates with our servers twice per session. The first time is when the session is started and sets the timestamp for the session, counts a new user or updates an existing user as active. The second time is when the session ends and all event data is sent in one batch.
In cases where we do not receive the second report we refer to this as an "incomplete session". This arises in a few scenarios but mainly
-No network connection when the session ends
-The app is sent to the background for >10 seconds and the session continues running
In these cases the event data is stored on the device's disk and sent the next time the app is launched.

Resources