Back to Insights
Wellness & Tech

Integrating Apple HealthKit and Google Fit: A Developer's Guide

Technical guide to integrating Apple HealthKit and Google Health Connect (formerly Google Fit) into your mobile app. Covers permissions, data types, and cross-platform strategies.

Marketplace Labs Team10 May 20266 min read
HealthKitGoogle FitHealth ConnectiOSAndroidAPI Integration

Integrating Apple HealthKit and Google Fit: A Developer's Guide

Health data integration is table stakes for fitness and wellness apps. Users expect their workouts, steps, and health metrics to sync with Apple Health and Google Fit. Here's how to implement it properly on both platforms.

The Landscape in 2026

iOS uses HealthKit, Apple's unified health data store. It's mature, well-documented, and consistent across devices.

Android has transitioned from Google Fit to Health Connect, a new platform-level API. Google Fit APIs are deprecated - new apps should use Health Connect exclusively.

Both platforms share similar concepts but have different APIs, permission models, and data schemas.

Apple HealthKit Integration

Setting Up Permissions

HealthKit requires explicit capability and usage descriptions:

<!-- ios/App/Info.plist -->
<key>NSHealthShareUsageDescription</key>
<string>We read your health data to track workout progress</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We save your workouts to Apple Health</string>

Enable the HealthKit capability in Xcode, then request permissions at runtime:

import HealthKit

let healthStore = HKHealthStore()

let typesToRead: Set<HKObjectType> = [
    HKObjectType.quantityType(forIdentifier: .heartRate)!,
    HKObjectType.quantityType(forIdentifier: .stepCount)!,
    HKObjectType.workoutType()
]

let typesToWrite: Set<HKSampleType> = [
    HKObjectType.workoutType()
]

healthStore.requestAuthorization(toShare: typesToWrite, read: typesToRead) { success, error in
    // Handle result
}

Reading Data

Query step count for today:

func getStepsToday(completion: @escaping (Double) -> Void) {
    let stepsType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
    let startOfDay = Calendar.current.startOfDay(for: Date())
    let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: Date())

    let query = HKStatisticsQuery(
        quantityType: stepsType,
        quantitySamplePredicate: predicate,
        options: .cumulativeSum
    ) { _, result, _ in
        let steps = result?.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0
        completion(steps)
    }

    healthStore.execute(query)
}

Writing Workouts

Save a completed workout:

func saveWorkout(
    type: HKWorkoutActivityType,
    start: Date,
    end: Date,
    calories: Double,
    distance: Double?
) {
    var metadata: [String: Any] = [:]

    let workout = HKWorkout(
        activityType: type,
        start: start,
        end: end,
        workoutEvents: nil,
        totalEnergyBurned: HKQuantity(unit: .kilocalorie(), doubleValue: calories),
        totalDistance: distance.map { HKQuantity(unit: .meter(), doubleValue: $0) },
        metadata: metadata
    )

    healthStore.save(workout) { success, error in
        // Handle result
    }
}

Background Delivery

HealthKit can notify your app when new data arrives, even in the background:

healthStore.enableBackgroundDelivery(
    for: HKObjectType.quantityType(forIdentifier: .stepCount)!,
    frequency: .hourly
) { success, error in
    // Now you'll receive updates via your observer query
}

Google Health Connect Integration

Setup

Health Connect requires Android 14+ or the Health Connect app on older versions. Add the dependency:

// build.gradle
implementation("androidx.health.connect:connect-client:1.1.0-alpha07")

Declare permissions in your manifest:

<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE"/>

<!-- Intent filter to appear in Health Connect's app list -->
<intent-filter>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE"/>
</intent-filter>

Checking Availability

Not all devices support Health Connect:

val availabilityStatus = HealthConnectClient.getSdkStatus(context)
when (availabilityStatus) {
    HealthConnectClient.SDK_AVAILABLE -> {
        // Ready to use
    }
    HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {
        // Prompt user to update Health Connect
    }
    HealthConnectClient.SDK_UNAVAILABLE -> {
        // Device doesn't support Health Connect
    }
}

Requesting Permissions

val permissions = setOf(
    HealthPermission.getReadPermission(StepsRecord::class),
    HealthPermission.getReadPermission(HeartRateRecord::class),
    HealthPermission.getWritePermission(ExerciseSessionRecord::class)
)

val requestPermissions = registerForActivityResult(
    PermissionController.createRequestPermissionResultContract()
) { granted ->
    if (granted.containsAll(permissions)) {
        // All permissions granted
    }
}

requestPermissions.launch(permissions)

Reading Steps

suspend fun getStepsToday(): Long {
    val client = HealthConnectClient.getOrCreate(context)
    val startOfDay = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()
    val now = Instant.now()

    val response = client.aggregate(
        AggregateRequest(
            metrics = setOf(StepsRecord.COUNT_TOTAL),
            timeRangeFilter = TimeRangeFilter.between(startOfDay, now)
        )
    )

    return response[StepsRecord.COUNT_TOTAL] ?: 0
}

Writing Workouts

suspend fun saveWorkout(
    startTime: Instant,
    endTime: Instant,
    exerciseType: Int,
    calories: Double
) {
    val client = HealthConnectClient.getOrCreate(context)

    val exerciseSession = ExerciseSessionRecord(
        startTime = startTime,
        endTime = endTime,
        exerciseType = exerciseType,
        startZoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime),
        endZoneOffset = ZoneOffset.systemDefault().rules.getOffset(endTime)
    )

    val caloriesRecord = TotalCaloriesBurnedRecord(
        startTime = startTime,
        endTime = endTime,
        energy = Energy.kilocalories(calories),
        startZoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime),
        endZoneOffset = ZoneOffset.systemDefault().rules.getOffset(endTime)
    )

    client.insertRecords(listOf(exerciseSession, caloriesRecord))
}

Cross-Platform Strategy

If you're building with React Native or Flutter, abstract the platform differences:

// healthService.ts
interface HealthService {
  requestPermissions(): Promise<boolean>;
  getStepsToday(): Promise<number>;
  saveWorkout(workout: Workout): Promise<void>;
  isAvailable(): Promise<boolean>;
}

// Platform-specific implementations
import { Platform } from 'react-native';
import { HealthKitService } from './healthkit';
import { HealthConnectService } from './healthconnect';

export const healthService: HealthService =
  Platform.OS === 'ios' ? new HealthKitService() : new HealthConnectService();

Handling Data Differences

HealthKit and Health Connect have different data models. Map to a common format:

interface NormalizedWorkout {
  id: string;
  type: 'running' | 'cycling' | 'strength' | 'hiit' | 'other';
  startTime: Date;
  endTime: Date;
  calories?: number;
  distance?: number; // meters
  heartRateSamples?: { timestamp: Date; bpm: number }[];
}

function normalizeHealthKitWorkout(hkWorkout: HKWorkout): NormalizedWorkout {
  return {
    id: hkWorkout.uuid,
    type: mapHKWorkoutType(hkWorkout.workoutActivityType),
    startTime: hkWorkout.startDate,
    endTime: hkWorkout.endDate,
    calories: hkWorkout.totalEnergyBurned?.doubleValue(for: .kilocalorie()),
    distance: hkWorkout.totalDistance?.doubleValue(for: .meter()),
  };
}

Common Integration Pitfalls

1. Requesting too many permissions upfront

Users are more likely to grant permissions if you ask for them contextually. Request step permissions when they open the step tracker, not at app launch.

2. Not handling permission denial

Always have a fallback. If a user denies health permissions, they can still log workouts manually.

3. Ignoring timezone complexity

Health data includes timezone information. A user who works out while travelling will have data in multiple timezones. Store everything in UTC, convert for display.

4. Forgetting about data deletion

Both platforms let users delete their health data. Your app should handle missing or deleted records gracefully.

5. Not testing on real devices

Health Connect doesn't work in Android emulators. HealthKit works in the iOS Simulator but behaves differently than on real hardware. Always test on physical devices.

Privacy Considerations

Health data is sensitive. Both Apple and Google require:

  • Clear explanation of why you need the data
  • Data to be used only for the stated purpose
  • Secure storage and transmission
  • The ability for users to delete their data

Apple is particularly strict during App Store review. Expect questions if you request health permissions.

Testing

HealthKit

The iOS Simulator includes a Health app where you can add test data. For automated testing, use HKHealthStore's test mode.

Health Connect

Use the Health Connect Toolbox app to add test data. For integration tests, mock the HealthConnectClient.

Next Steps

Health data integration adds significant value to fitness apps, but it's also one of the more complex integrations to get right. Start simple - steps and basic workouts - then expand based on what your users actually need.

If you're building a fitness or wellness app and want to get health integration right the first time, reach out. We've integrated HealthKit and Health Connect for multiple fitness startups.