Google Fit Integration for Health Data Access in Android
Google Fit API exists since 2014 and today is in "works, but better migrate to Health Connect" state. Google officially recommends moving to Health Connect for new projects. Still, Google Fit remains relevant for Android 8–13 devices without Health Connect support, Wear OS apps and projects with existing user base. Cover both approaches.
Google Fit REST API vs Fitness API
Two fundamentally different entry points:
Android Fitness API (com.google.android.gms:play-services-fitness) — native Java/Kotlin SDK, works via Google Play Services, requires OAuth 2.0 Google account.
Google Fit REST API — HTTP API, suitable for server-side and Flutter/React Native, requires own OAuth token management.
For native Android — always Fitness API. REST makes sense only if data needed on backend without mobile device.
Permissions and OAuth: Main Problem Source
Google Fit requires two permission levels:
-
Android permission:
android.permission.ACTIVITY_RECOGNITION(Android 10+) -
OAuth scope:
FITNESS_ACTIVITY_READ,FITNESS_BODY_READ,FITNESS_LOCATION_READetc.
Request Android permission but not OAuth scope — Fitness API returns empty data without error. Silent failure, hard to catch.
val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ)
.build()
val account = GoogleSignIn.getAccountForExtension(this, fitnessOptions)
if (!GoogleSignIn.hasPermissions(account, fitnessOptions)) {
GoogleSignIn.requestPermissions(
this,
GOOGLE_FIT_REQUEST_CODE,
account,
fitnessOptions
)
}
User revokes permission via Google account settings (not Android Settings), hasPermissions() returns false next launch. Must handle — without retry logic app simply stops receiving data.
Reading Data: HistoryClient and SensorsClient
Historical Data (steps, calories)
val readRequest = DataReadRequest.Builder()
.read(DataType.TYPE_STEP_COUNT_DELTA)
.aggregate(DataType.AGGREGATE_STEP_COUNT_DELTA)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build()
Fitness.getHistoryClient(context, account)
.readData(readRequest)
.addOnSuccessListener { response ->
response.buckets.forEach { bucket ->
val steps = bucket.dataSets
.flatMap { it.dataPoints }
.sumOf { it.getValue(Field.FIELD_STEPS).asInt() }
}
}
bucketByTime — key method for aggregation. Without it request returns each step from each source (phone + watch + band), possibly thousands records per day.
Real-time Data
SensorsClient for live data subscription:
Fitness.getSensorsClient(context, account)
.add(SensorRequest.Builder()
.setDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.setSamplingRate(10, TimeUnit.SECONDS)
.build(),
onDataPointListener
)
Subscriber active only while app foreground. For background tracking — RecordingClient.subscribe(), which Google Fit accumulates itself.
Data Deduplication from Multiple Sources
Real pain: user has Apple Watch (via Health) + Google Fit on Android phone + Samsung Health — steps double and triple. Google Fit partially solves via DataSet.getDataSources() — each data point has source (DataSource). Filtering by DataSource.DEVICE gets data only from specific device.
No fully reliable deduplication — known ecosystem problem. Document expected discrepancies to client and build UI so user can select priority source.
Migration to Health Connect
For new devices (Android 14+) Google Fit deprecated at recommendation level. Strategy: check Health Connect availability, use if available, fallback to Google Fit for old devices:
val healthConnectAvailable = HealthConnectClient.getSdkStatus(context) ==
HealthConnectClient.SDK_AVAILABLE
Timeframes
Basic Google Fit integration (steps, distance, calories) — 4–7 work days. With Health Connect support, deduplication and Wear OS — 2–4 weeks.







