Repository navigation
Apple Health Import
Status: Current implemented behavior. Audience: Users and contributors. Implementation:
features/imports/applehealth. Navigation:Screen.SettingsDataImport, settings sectionDATA_IMPORT. Related: Feature map, Settings and preferences, Permissions.
OpenVitals can analyze supported records from Apple Health exports, let the user choose which categories to import, and then write the selected Health Connect-compatible records with duplicate checks, route handling, and a downloadable full report.
The import starts from Settings and accepts Apple Health export.xml or export.zip files. Zipped exports may also contain Apple workout route files under workout-routes/*.gpx; these are matched to WorkoutRoute file references in export.xml.
Large exports run as user-started background work so the import can continue after leaving the Settings screen.
The app scans the export with a streaming SAX parser rather than loading the whole XML document into memory. External DTD/entity loading is disabled so Apple export doctypes do not force network or grammar resolution, and Apple date strings keep their timezone offsets when records are mapped to Health Connect.
The first pass is analysis-only. Before decompression, OpenVitals copies the compressed export atomically into app-private storage, flushes it to disk, and verifies the copied byte count against the document provider's advertised size when one is available. Analysis then reads that stable local copy, and a successful copy is reused by the background import instead of reading the provider again. A short provider copy fails before multi-gigabyte XML inflation begins; any analysis failure clears the staged file so retrying cannot reuse it.
The analyzer streams the staged export, maps Apple types to import categories, and shows detected import categories such as workouts and routes, activity metrics, heart, sleep, body, vitals, nutrition, hydration, mindfulness, and cycle tracking. Nothing is written to Health Connect during this pass, and the analyzer does not parse GPX route geometry or construct Health Connect records just to build the checkbox menu. Analysis only needs each record's Apple type, so it skips parsing timestamps, metadata, and numeric values. It streams export.xml directly from the compressed local copy without extracting a multi-gigabyte temporary XML file, keeping memory and storage use bounded by the compressed export plus streaming buffers.
After analysis, the user can select or unselect categories with checkboxes. The import pass then re-parses the same export and writes only converted records whose Health Connect target category is selected. Known unselected record types are counted directly at the SAX boundary using a shared lightweight skip marker: the importer does not parse their dates and values, allocate metadata maps, build conversion objects, or construct Health Connect records. Distance and active-energy samples are the narrow exception when selected workouts need them for overlap protection. Workout GPX entries are parsed only when Workouts and routes is selected; otherwise the importer streams export.xml and stops reading the ZIP as soon as that entry is complete. In Apple's normal export ordering, where workout routes follow export.xml, later unselected damaged routes therefore cannot fail a Sleep, Body, Vitals, Nutrition, Hydration, or Mindfulness import. A nonstandard archive that puts entries before export.xml still has to be traversed sequentially to locate it. This keeps allocation and memory pressure proportional to selected data even though every XML element must still be traversed.
If a ZIP ends unexpectedly while the full import is reading a workout-routes/*.gpx entry, OpenVitals can continue only when export.xml was already copied intact. It imports the health records and any workout routes parsed before the damaged entry, marks workout routes as incomplete in the result, and records a route_archive_truncated diagnostic in the report. Workout sessions whose referenced GPX files were unavailable are imported without route geometry and listed under Activities Requiring Manual Route Import with their Apple activity type, time range, and missing file path so the user can identify them for manual recovery. The damaged route and remaining ZIP entries are unavailable. Damage before export.xml is complete still fails the import rather than accepting partial health data.
The importer requests required Health Connect write permissions before writing accepted records into Health Connect. Large exports are processed with targeted lookups and time-window chunking to reduce memory pressure. Progress and result counts are shown while the import runs.
Import progress is split into explicit stages: scanning the export, converting records, checking duplicates, writing records, and building the report. During the import pass, the determinate percentage uses raw elements scanned against the total measured during analysis, so long stretches of unselected records still advance visibly. Selected-record preparation and imported counts remain visible alongside it. The worker adds periodic progress heartbeats to the downloadable report, and the report logs start and finish entries for each stage, including batch counts where available, so a slow scan can be distinguished from a stalled worker.
Parsing/converting and the Health Connect duplicate-check/write steps run concurrently on a producer/consumer pipeline instead of blocking each other: while one batch of converted records is being checked and written, the parser keeps reading and converting the next batch. Health Connect duplicate lookups for a batch also run as several bounded concurrent queries instead of one at a time. Both changes shorten wall-clock import time without changing which records get imported, deduplicated, or skipped.
Result summaries can include parsed, imported, duplicate, not selected, unsupported, skipped, and failed counts.
Supported imports cover activity, heart, body, hydration, nutrition, sleep, mindfulness, vitals, and cycle records where Health Connect has compatible record types and write permissions are granted.
Unsupported or incompatible records are skipped with diagnostics rather than forcing partial data into the wrong Health Connect type.
Clinical records are not part of this import. The analysis counts the export's ClinicalRecord entries, and where Health Connect offers medical records, a card under the Apple Health card says how many. It opens the medical records import on the same file, with its own review and patient check. See Medical records.
Workout imports include WorkoutStatistics totals when available. Walking speed records map to Health Connect speed samples. Apple workout GPX route geometry is written as a Health Connect exercise route when a workout references a route file and the route has at least two valid points.
Apple GPX route timestamps are not trusted for Health Connect writes because they can be duplicated or outside the workout interval. OpenVitals synthesizes strictly increasing route point times inside the workout start/end window, weighted by cumulative route distance, at the millisecond precision Health Connect stores route locations with; this keeps paused or stationary GPS segments (repeated identical coordinates) from collapsing onto the same timestamp, which would otherwise break route reads and re-import duplicate detection. Placeholder all-zero altitude values are omitted.
The importer skips exact duplicates inside the same export and checks existing imported Health Connect records by deterministic clientRecordId before writing selected records.
For additive metrics where double-counting is common, such as steps, active calories, and distance, overlapping records from multiple Apple Health sources are merged conservatively. Lower-priority samples that are already mostly covered by another source are skipped with an overlap_cross_source diagnostic. Workout distance and active calorie totals are not written when overlapping raw records already cover the same workout interval.
After an import finishes, Settings can download a text report. The report starts with a short summary, then includes selected categories, importer logs, per-category counts, per-type counts, grouped diagnostics, raw diagnostics, and worker logs. A recovered truncated workout-route section is shown as a partial-route warning and route_archive_truncated diagnostic rather than failing the health-record import. If the import fails before completion, the failure report includes the summary, worker logs, and the masked exception stack trace.
Workouts that referred to a route file the export does not hold are listed on the import card, by activity and local date, so each route can be brought in with the route importer. The list is kept in a private file beside the report; the report itself only counts them by type.
The reports are meant to be shared, so they hold structure, not data: record types, units, reasons and counts, the import log without URIs, file locations or time ranges, a parse error's line and column without the text around it, and error text masked by PrivacyRedactor. They hold no record's time or value, no source's name and no file name. Grouped diagnostic counts (for example, how many records were skipped for a given reason) are always complete; the raw per-record diagnostic log underneath is capped at 1,000 entries per source so that re-importing an already-imported export cannot grow the report to an unbounded size.
Contributors can run the importer parser/converter on a desktop JVM without building or installing the whole app:
./gradlew app:testCiUnitTest \
--tests tech.mmarca.openvitals.features.imports.applehealth.AppleHealthImportSmokeTest \
-PappleHealthExport=/path/to/export.zip \
--console=plainThe appleHealthExport value may point to export.zip, export.xml, or an unzipped Apple Health export directory. The smoke test parses XML, parses GPX route files when present, converts supported records, and prints counts including parsed records, unsupported entries, skipped records, failures, route-backed sessions, and GPX files.
Imported records are written to Health Connect. The compressed export is staged only in app-private local storage for analysis and the pending background import; it is cleared after a successful import or an analysis failure, replaced when another export is staged, and removed with the app's private data. OpenVitals does not upload the export to an OpenVitals server and does not provide a bulk rollback after records are written.
Generated from docs/features/apple-health-import.md. Edit it there; changes made here are overwritten.
This wiki mirrors the Markdown in Hale-MTU/OpenVitals on every push to main.
Features
- Features
- Achievements
- Activity And Training Plans
- Activity Metrics
- Apple Health Import
- Beverage Logging And Caffeine
- Bluetooth LE Sensors
- Body Energy
- Body Metrics
- CoMaps Navigation Context
- CSV Import
- Cycle Tracking
- Daily Readiness
- Elevation Correction
- Feature Map
- FIT Files Import
- Food Logging
- GPX/KML/KMZ/TCX Route Import
- Health Connect Metrics Dashboard
- Health Report Export
- Heart And Vitals
- Heart Rate Recovery
- Home Screen Widgets
- Hydration
- Manual Entry Of Metrics
- Medical Records
- Metric Detail Customization
- Mindfulness
- Non Health Connect Metrics Dashboard
- Nutrition
- Offline Maps Support
- Onboarding And Permissions
- Preloaded Beverage Nutrition Reference
- Privacy, Support, And Diagnostics
- Recording Of Activity
- Reminders
- Settings And Preferences
- Sleep Score And Recovery
- Sleep Tracking
- Statistics
- Sync With Another Phone
How-to
App
- App Guide
- FAQ
- Getting Started
- Health Connect
- Install
- Local And Connected Editions
- Permissions
- Privacy
- Screenshots
- Support
Reference
- Design Reference
- Reference
- Design Guidelines for Offline & Sync
- OpenVitals UI Notes
- Reference: Data Capture
- Reference: Data Display
- Reference: Health Connect Matchmaking
- Reference: Health Connect Onboarding
- Reference: Health Connect Permissions
- Reference: Health Connect UX
- Reference: Promoting Health Connect
Engineering
- Code Analysis
- Engineering
- Flutter ↔ Kotlin test-parity matrix
- apple-health-import
- Architecture
- body-cycle-recovery
- Design system conformance
- Development
- Feature Playbook
- Flutter -> Kotlin test parity: domain/data/core/contract/state/navigation/di/bootstrap/l10n/widget_test
- Flutter → Kotlin test-parity matrix: features section
- Flutter → Kotlin test-parity matrix: ui / goldens / integration / fixtures
- Flutter-to-Kotlin test-parity matrix — dashboard + devicesync
- manual-entry
- Translations
Proposals
- Proposals
- Activity Start Flow Analysis
- Activity Start Flow Simplification Proposals
- Caffeine Detail And Future Sleep Insights
- Cycle Tracking: Design and Status
- Medical Records Through Health Connect
- Medical Records: What Is Left
Releases
- OpenVitals 2.16.0
- OpenVitals 2.15.2
- OpenVitals 2.15.1
- OpenVitals 2.15.0
- OpenVitals 2.14.5
- OpenVitals 2.14.4
- OpenVitals 2.14.3
- OpenVitals 2.14.2
- OpenVitals 2.14.1
- OpenVitals 2.14.0
- OpenVitals 2.13.0
- OpenVitals 2.12.0
- OpenVitals 2.11.2
- OpenVitals 2.11.1
- OpenVitals 2.11.0
- OpenVitals 2.10.1
- OpenVitals 2.10.0
- OpenVitals 2.9.1
- OpenVitals 2.9.0
- OpenVitals 2.8.0
- OpenVitals 2.7.3
- OpenVitals 2.7.2
- OpenVitals 2.7.1
- OpenVitals 2.7.0
- OpenVitals 2.6.6
- OpenVitals 2.6.5
- OpenVitals 2.6.4
- OpenVitals 2.6.3
- OpenVitals 2.6.2
- OpenVitals 2.6.1
- OpenVitals 2.6.0
- OpenVitals 2.5.0
- OpenVitals 2.4.1
- OpenVitals 2.4.0
- OpenVitals 2.3.2
- OpenVitals 2.3.1
- OpenVitals 2.3.0
- OpenVitals 2.2.8
- OpenVitals 2.2.7
- OpenVitals 2.2.6
- OpenVitals 2.2.5
- OpenVitals 2.2.4
- OpenVitals 2.2.3
- OpenVitals 2.2.2
- OpenVitals 2.2.1
- OpenVitals 2.2.0
- OpenVitals 2.1.0
- OpenVitals 2.0.4
- OpenVitals 2.0.3
- OpenVitals 2.0.2
- OpenVitals 2.0.1
- OpenVitals 1.9.0
- OpenVitals 1.8.0
- OpenVitals 1.7.7
- OpenVitals 1.7.6
- OpenVitals 1.7.5
- OpenVitals 1.7.4
- OpenVitals 1.7.3
- OpenVitals 1.7.2
- OpenVitals 1.7.1
- OpenVitals 1.7.0
- OpenVitals 1.6.3
- OpenVitals 1.6.2
- OpenVitals 1.6.1
- OpenVitals 1.6.0
- OpenVitals 1.5.1
- OpenVitals 1.5.0
Project