Inspired by _DavidSmith, I'm going to try posting more-frequent, medium-length technical posts here. Objectives: share my learnings, solidify my knowledge, and open myself up to new ideas and feedback.
I learned yesterday that I need to write these posts in the morning, otherwise they won't get written. By the time I sat down to write one, I had mostly forgotten what I did the day before.
Fun Times with HealthKit
Yesterday morning was spent mostly working to fix and/or mitigate a weird HealthKit error someone in the FoodNoms Discord reported.
Their issue: foods were no longer being written to the Health app.
They have been very helpful in sending me diagnostics reports. In the reports, this is the related error:
Could not find existing object of class HDCorrelationSampleEntity to delete with external sync identifier \'73933C25-716D-4517-A679-80913117B22B\' (object ID 5701400)
Also saw some other errors like this:
The connection to service named com.apple.healthd.server was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid
I searched Google and GitHub for these errors, but didn't come up with any relevant results. The one hit that came up seemed to be related to failing to request for authorization, but I know that's not what is happening here.
Anyways, I improved my logging to nail down exactly where the error was being thrown. To my surprise, the error wasn't occurring when deleting food correlations, but when saving them. Very puzzling.
At this point, I took a step back to think about what could be going on:
- Health is "angry" at FoodNoms and is intentionally or unintentionally throttling the app
- The Health DB has some sort of data corruption and HK is not handling it well
- Some bug in HealthKit/iOS; don't know what
Without knowing more about what exactly is the root cause, what can be done?
- If the issue arises due to overloading the HealthKit API, I could slow down or reduce the number of samples I save at once.
- If there's nothing the app can do, is there something the customer can do? I can then document this in a FAQ post and have a canned response for support emails.
- If the issue is related to an isolated sample or correlation, I could handle this by trying again but excluding that specific data point. But how could I do this? The error has a defaults dictionary, but it's empty. I could check the error code (100), but that seems to be a catch-all for various undocumented errors. So that doesn't leave me any other choice but do the dirty thing of parsing the localizedDescription, check for "HDCorrelationSampleEntity", and parse out a UUID. Then try again but exclude that specific sample/correlation.
- If the issue is time-specific and recoverable, I could improve the retry mechanism.
I ended up doing #1 and #4. I'm now batching saves in groups of 8 instead of a max of 400. I'm also auto-restarting processing after the next time the app is launched to the foreground. I needed to implement similar behavior with CloudKit syncing, because the CloudKit daemon can randomly crash or fail as well.
I worked with the beta tester to see if there's anything we can do for #2, but no luck. We tried deleting all data from FoodNoms in the Health app. Tried restarting the device. Reinstalling the app, etc.
I have two reservations about #3. One is that in the logs, it seems that it's not just a single UUID that is repeatedly showing up in the logs. Each time it's a different one. The other issue is that this could potentially lead to inconsistency between FoodNoms and the Health app.
Meanwhile, the beta tester updated their device to the latest OS, 16.2, and poof – the issue went away. Yay??
I have no idea how likely this issue is to come up for others. Was this just a freak, isolated event? Was this a bug with iOS 16.1 that was fixed in 16.2?
Regardless, there's a lesson learned here that I can't rely on HealthKit to be fully cooperative. I need to build more user-visible diagnostics, display of progress, and error reporting. I spent a lot of time doing similar work for CloudKit, so I can carry over a lot of the same design decisions to HealthKit.
Iterating on Pitch and Pricing
In the PM, I spent more time refining my pitch for FoodNoms 2. Also thought more about what I want to put behind the paywall. I've gone back and forward about what should be paid-only for weeks now. I've been reading and listening to others talk about frameworks for pricing in freemium products. After sitting down and doing some focused thinking about it, I think I've finally settled on a set of decisions. Here are the principles I'm following:
- Start with making features paid as a default. It's easier to make things free later than the reverse.
- If the feature will dramatically help with onboarding and activation, then make it free. Otherwise you risk burying the customer's "a ha" moment that would ultimately lead them to convert to paid.
- If a feature is likely to generate more support requests and is in general "more complex", make it paid-only.
- Build some paid-only features that are visible closer to the "top of the funnel". (You look at FoodNoms today and almost all of the paid features are at the bottom of various user flows.)
That's it for my first dev notes post. Let me know if you learned something, thought this was interesting, or have any other feedback to share! These days I like to hang out over on Mastodon.