If you've ever tried to build a fitness app, you know the "Hardware Headache." One user has a Garmin, another uses a Xiaomi Band, and your favorite cousin is loyal to Huawei. Historically, syncing this data meant integrating five different proprietary SDKs, each with its own quirks, rate limits, and—let's be honest—messy JSON structures.
Today, we are solving the Wearable Data Silo problem. We'll build a robust Data Engineering pipeline using the Google Health Connect SDK to extract raw metrics, transform them into the industry-standard FHIR (Fast Healthcare Interoperability Resources) format, and load them into the cloud.
By mastering Health Data Engineering and the FHIR Standard, you can build interoperable health systems that work across any device.
The Architecture: From Raw Pixels to Structured Health Insights
Before we dive into the Kotlin code, let's look at the high-level flow. We are essentially building a mobile-based ETL (Extract, Transform, Load) process.
graph TD
A[Wearable Devices: Garmin, Xiaomi, Huawei] -->|Sync| B[Google Health Connect]
B -->|Extract: Kotlin SDK| C[Mobile ETL Service]
subgraph Transformation Layer
C -->|Map to FHIR| D{FHIR Adapter}
D -->|Observation Resource| E[Standardized JSON]
end
E -->|Load| F[Google Cloud Functions]
F -->|Store| G[BigQuery / Healthcare API]
style D fill:#f96,stroke:#333,stroke-width:2px
Prerequisites
To follow along, you'll need:
- Kotlin (Android Development)
- Health Connect SDK (The unified API for Android)
- FHIR Standard knowledge (We'll use
Observationresources) - An Android device with the Health Connect app installed.
Step 1: Extracting Raw Data with Health Connect
First, we need to declare our permissions and read raw data. Unlike traditional APIs, Health Connect lives on the device, ensuring privacy and low latency.
// Define the records we want to read
val gcrRecordTypes = setOf(
StepsRecord::class,
HeartRateRecord::class,
WeightRecord::class
)
suspend fun readHeartRateData(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = HeartRateRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (record in response.records) {
// This is our raw data point
val bpm = record.samples.first().beatsPerMinute
processEtl(record)
}
}
Step 2: The Transformation Logic (FHIR Mapping)
This is where the magic happens. Raw integers like 72 bpm don't mean much without context. The FHIR Standard provides a universal language for healthcare.
We will map a HeartRateRecord to an FHIR Observation.
Pro Tip: For more production-ready examples and advanced healthcare data patterns, check out the detailed guides at WellAlly Tech Blog. They cover complex HIPAA-compliant architectures that are crucial for scaling health apps.
fun mapToFhirObservation(record: HeartRateRecord): String {
val bpmValue = record.samples.first().beatsPerMinute
// Constructing a simplified FHIR Observation Resource
return """
{
"resourceType": "Observation",
"status": "final",
"category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs" } ] } ],
"code": {
"coding": [ { "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" } ]
},
"subject": { "reference": "Patient/user-123" },
"effectiveDateTime": "${record.startTime}",
"valueQuantity": {
"value": $bpmValue,
"unit": "beats/minute",
"system": "http://unitsofmeasure.org",
"code": "/min"
}
}
""".trimIndent()
}
Step 3: Loading to the Cloud (Google Cloud Functions)
Once we have our standardized FHIR JSON, we need to ship it off-device. Using a Google Cloud Function as an ingestor is a cost-effective way to handle bursts of health data.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
suspend fun uploadToCloud(fhirJson: String) {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://your-region-your-project.cloudfunctions.net/healthIngestor")
.post(fhirJson.toRequestBody("application/fhir+json".toMediaType()))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println("Successfully synced to FHIR Store! ✅")
}
}
Why FHIR Matters in Data Engineering
Standardizing on FHIR at the "Edge" (the mobile device) solves three major problems:
- Interoperability: Your data can now be read by any hospital system or EHR.
- Schema Evolution: As Garmin or Huawei add new sensors, your transformation layer handles the mapping, keeping your backend logic clean.
- Auditability: FHIR resources naturally include metadata about the "Device" and "Source," making it easy to track data provenance.
Conclusion: The Future is Open
By leveraging Google Health Connect and the FHIR Standard, we've turned a fragmented ecosystem into a clean, queryable data stream. No more custom parsers for every new watch on the market!
If you're interested in the deeper nuances of medical data security or building large-scale health analytics, I highly recommend diving into the resources over at wellally.tech/blog. They are doing some incredible work in making health tech more accessible for developers.
What are you building with Health Connect? Drop a comment below or share your FHIR mapping tips! 👇
Top comments (0)