All work

2026 · Case study · Phill Morgan

iOS Health Data Export Application

A privacy-first React Native app for exporting Apple HealthKit data to portable formats, with scheduled API syncing and on-device-only processing.

Available for portfolio review on request.

Stack

  • React Native
  • Expo 55
  • TypeScript
  • Apple HealthKit
  • NativeWind
  • Expo Router
  • RevenueCat
  • Expo Secure Store
  • Background Fetch
  • fflate

Role & Scope

Solo build. Architecture, Swift + Expo development, App Store release, ongoing maintenance.

Overview

A focused iOS utility that solves one specific problem: giving users ownership of their Apple Health data in portable, usable formats. The app reads over 100 health data types from HealthKit, processes them entirely on-device, and exports them as structured CSV or JSON, ready for personal analysis, sharing with a healthcare provider, or importing into another platform.

Health data never leaves the device unless the user explicitly triggers an export or configures their own API endpoint for automated sync. The app has no backend, no cloud storage, and no server-side data processing of any kind.

Key decisions

On-device zero-knowledge processing over cloud sync. A conventional architecture would have uploaded HealthKit data to a backend, processed and stored it server-side, and offered a web dashboard. I chose the opposite: every byte of processing happens on the device, nothing is persisted by the app, exports are handed off through the native iOS share sheet. That meant trading multi-device continuity and server-side analytics for zero breach surface and zero ongoing infrastructure cost. In hindsight: no incidents to respond to, no DPA to negotiate, and the on-device story is what users tend to mention first.

HealthKit anchored queries over full re-reads on each sync. The naïve path for the scheduled API sync would have been to query HealthKit for everything and filter client-side to “new since last sync.” For active users that’s tens of thousands of samples per query. Anchored queries are HealthKit’s incremental read primitive: you hand it the anchor from the last query and it returns only what changed since. The trade is a small amount of state (the anchor per data type, in local storage) against much smaller queries and much faster syncs.

Expo managed workflow over bare React Native. The app sits at the boundary of where Expo managed works: it needs HealthKit (via a community config plugin), Background App Refresh, Keychain access, RevenueCat. The bare workflow would have given unlimited native flexibility but meant maintaining a full iOS project, an Xcode build config, and the associated maintenance tax. Expo managed let me ship and iterate fast, and so far nothing the app needs has fallen outside what the managed workflow and its config plugins can do. The line I’d watch: if HealthKit’s API surface extends beyond what the community plugin keeps current, I’d need to re-evaluate.

Health Data Coverage

The app reads from 14 HealthKit categories, spanning over 100 individual data types:

  • Activity: steps, distances, flights climbed, active energy, exercise minutes, VO2 Max
  • Heart: heart rate, HRV, resting heart rate, blood pressure, blood oxygen, AFib burden
  • Body Measurements: weight, height, BMI, body fat percentage, lean body mass
  • Mobility: walking speed, step length, asymmetry, stair speed, walking steadiness
  • Sleep: sleep analysis, wrist temperature, breathing disturbances
  • Nutrition: 27 types covering macronutrients, vitamins, minerals, caffeine, and water
  • Respiratory, Vitals, Hearing, Mindfulness, Symptoms, Running, Cycling: full HealthKit coverage

On launch the app probes HealthKit to detect which data types have recent records on the current device, then shows the user only the categories that actually contain data.

Export Pipeline

Users pick between two export modes:

  • Summary: daily aggregates with sum, average, min, and max per day, ideal for trend analysis over long periods
  • Detailed: every individual sample HealthKit has recorded, with timestamps intact

The export queries HealthKit in monthly chunks to manage memory pressure, caps any single data type at 100,000 records, and packages the output as a ZIP containing one CSV or JSON file per category plus a metadata manifest. Progress is reported live as the pipeline moves through querying, writing, compressing, and completion.

The finished ZIP is delivered through the native iOS share sheet: email, Files app, AirDrop, or any other sharing target.

Automated API Sync

A premium feature lets users configure a custom HTTPS endpoint to receive health data on a schedule. The sync system supports five authentication methods (Bearer token, API key with custom header, Basic Auth, custom headers, or no authentication), with credentials stored in the iOS Keychain via Expo Secure Store.

Syncs run through iOS Background App Refresh, either hourly or daily, and only transmit data recorded since the last successful sync. Every attempt is logged with timestamp, record count, HTTP status, and error detail. Users can browse sync history, test their endpoint, trigger a manual sync, and pause syncing without losing configuration.

Network preference controls ensure syncs only run on WiFi, cellular, or either, depending on what the user wants.

Monetisation

RevenueCat manages a freemium model with three purchase options: monthly subscription, annual subscription, and a lifetime one-time purchase. The free tier covers the health data overview and category browsing. Premium unlocks export and API sync configuration.

Technical Architecture

  • Navigation: Expo Router with a five-tab layout (Home, Export, API Sync, Subscription, Settings)
  • State management: React Context providers for health data availability, subscription state, API sync configuration, and app refresh triggers
  • HealthKit access: @kingstinct/react-native-healthkit for reading quantity and category samples, with anchored queries for incremental fetching
  • File handling: expo-file-system for writes, fflate for ZIP compression at level 6, expo-sharing for the native share dialog
  • Styling: NativeWind (Tailwind for React Native) for a consistent design language across every screen
  • Background processing: expo-background-fetch and expo-task-manager for scheduled sync tasks, with @react-native-community/netinfo for connectivity checks
  • Security: auth credentials in Expo Secure Store, health data never written to app storage or transmitted to any first-party server