Skridt, distance, kalorier, hjertefrekvens og søvn skal hentes fra brugerens platform (Android Health Connect på Android, Apple HealthKit på iOS) af en sundheds- og ernæringsapp — og præsenteres som én ren, deduplikeret daglig profil. Der er meget få ligheder mellem de to API'er: separate læsemekanismer, forskellige autorisationsordninger og forskellige datastrukturer. Vi byggede det mobile synkroniseringslag, der får dem til at fremstå som ét.
Udfordringen
- To native API'er, der intet er enige om. Apple HealthKit returnerer daglige aggregater via callbacks; Health Connect returnerer rå, sideinddelte prøver med rig kildemetadata. Samme koncepter, helt forskellige former — og appen skulle normalisere begge til ét skema.
- Dobbeltoptælling er standard. En telefon, et smartwatch og en tredjepartsapp kan alle rapportere de samme 8.000 skridt. Naiv opsummering puster alle målinger op. Appen skulle genkende overlappende kilder og tælle hver virkelige aktivitet én gang.
- Batteri og netværk kan ikke betale for friskhed. Sundhedsdata ændrer sig hele dagen, men at læse hele historikken ved hver synkronisering ville dræne batteriet og mætte mobilforbindelser. Synkroniseringskadencen skulle forblive let, mens den stadig føltes live.
Tilladelser kan være forvirrende. Snesevis af datatyper, to tilladelsesmodeller, baggrundslæsningsbegrænsninger og brugere, der giver visse tilladelser, men ikke andre — alt dette skal appen håndtere uden at crashe flowet.
Vores løsning
Vi byggede en tre-lags synkronisering: et tyndt nativt lag læser hver platforms API, et normaliseringslag på enheden deduplikerer og samler dataene i daglige opsummeringer, og backend'en gemmer det og afstemmer det i en enkelt pålidelig hovedbog. Klienten sender aldrig rå prøver — den sender rene, tidszone-korrekte daglige opsummeringer.

Arkitektur
- iOS læser Apple HealthKit via react-native-health; Android læser Health Connect via react-native-health-connect.
- Normalisering på enheden omdanner begge platformes output til ét skema og deduplikerer, før noget forlader telefonen.
- Synkroniseringstriggere — 30 dages historik ved første lancering, derefter lette 1-dags inkrementelle læsninger med et 10-minutters interval og ved hver app-genoptagelse.
- Transport — normaliserede daglige opsummeringer POSTes til hovedserverens /user-health/system-activity, som videresender dem (med bruger-ID + tidszone) til sundheds-microservicen.
- Persistens — hver kilde/dag lagres idempotent som en HealthSystemAggregate; en MongoDB change stream afstemmer den derefter til en daglig HealthLedger.
Expo / React Native styret workflow med native HealthKit- og Health Connect-moduler.
Nøglefunktioner
- Én normaliseringskontrakt for to API'er. Platformspecifikke læsere fører et enkelt formatHealthData trin, der udsender den samme form uanset kilde — så backend og UI aldrig forgrener sig på iOS vs Android.
2. Maksimum-baseret deduplikering på enheden. Inden for hver kilde summeres en dags aflæsninger; derefter er den daglige værdi Math.max() på tværs af kilder (kalorier indtastet efter source|deviceType), så flere aflæsninger fra én enhed akkumuleres, mens et ur og en telefon, der rapporterer den samme dag, ikke dobbelttæller:
// Sum readings within each source, then take the max ACROSS sources dateSourceMap[date][source] += entry.count; const totalSteps = Math.ceil(Math.max(...Object.values(dateSourceMap[date]))); |
3.Den rigtige læsestrategi pr. platform. Health Connect-læsninger er sideinddelte med en pageToken-løkke (1.000 poster/side); HealthKit's daglige aggregater loopes dag for dag. Hver særhed er indeholdt i sin egen læser, usynlig for resten af appen.
4. En synkroniseringskadence, der respekterer batteriet. En fuld 30-dages tilbagefyldning kører kun én gang (beskyttet af et første-kald-flag); derefter trækker hver synkronisering kun én dag — hurtigt på mobildata, billigt på strøm.
5. Tidszone-korrekt daglig gruppering. Prøver inddeles i YYYY-MM-DD-nøgler i brugerens egen tidszone, så en træning kl. 23:00 lander på den rigtige dag, uanset hvor serveren er placeret.
6. Idempotente backend-skrivninger. Hver opsummering upsertes på (userId, deviceId, source, date) — genafsendelse af samme dag er en no-op, hvilket gør genforsøg og overlappende synkroniseringer sikre.
7. Ærlig nedbrydning. Manglende tilladelser, tomme læsninger og Health Connect rate-limit-fejl fanges og vises pænt — en tom dag crasher aldrig synkroniseringen, og brugeren får en klar meddelelse i stedet for en tavs fejl.
Resultater
- En enkelt, konsistent sundhedsprofil på tværs af iOS og Android — platformforskellen er usynlig for resten af appen.
- Overlappende enheds-/app-kilder deduplikeres, så skridt og kalorier afspejler virkeligheden i stedet for oppustede summer.
- Lette inkrementelle synkroniseringer holder data friske uden at dræne batteriet eller forbruge mobildata.
Tidszone-korrekte, idempotente skrivninger betyder, at hver måling lander på den rigtige dag, og genforsøg korrumperer aldrig posten.
Teknologistak
React Native (Expo) · react-native-health (HealthKit) · react-native-health-connect · NestJS · MongoDB · MongoDB Change Streams · moment-timezone

