Skip to main content

SmartWalking (Health Tracking)

SmartWalking connects a member’s device health data to ActiveFit+. On iOS, it integrates with Apple HealthKit; on Android, with Google Health Connect. The SDK can also read locally via AFHealthTracking for permission checks or raw reads. Production activity data appears in Activities only after it is synced to—and processed by—the server.


What you can build

  • A first‑run Connect Health flow that requests platform permissions and registers SmartWalking.
  • A Sync Steps action that pushes recent steps and shows “last synced” status.
  • A Diagnostics pane that uses AFHealthTracking to review permission state and read a day’s raw metrics for support.

Connect to Apple HealthKit (iOS)

let vendorId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
let ok = try await AFCore.shared.smartWalking().connectAppleHealthKit(deviceId: vendorId)

This presents the Health authorization sheet (if needed) and registers SmartWalking for the member. When granted, your app is ready to sync.


Connect to Google Health Connect (Android)

val ok = withContext(Dispatchers.IO) {
AFCore.smartWalking().connectGoogleHealthConnect(activity, deviceId)
}

activity must be the current Activity. The SDK launches the Health Connect consent UI (if needed) and registers SmartWalking.


Permissions & background behavior

iOS (HealthKit)

  • Health data syncing typically occurs while the device is unlocked and the app is in a viable execution state. If the user doesn’t unlock the phone for extended periods, data windows can be missed.
  • The SDK schedules step syncs every 3–4 hours, but iOS background execution is opportunistic; the system may delay tasks.
  • If data hasn’t synced for a while, remind the user to open the app. (Many teams send a push notification or in‑app reminder.)

Android (Health Connect)

  • For reliable background sync, users must grant background execution conditions (system battery/optimization settings) and have internet connectivity. The SDK uses a worker that runs when constraints are met (e.g., not low battery, network available).
  • The connectGoogleHealthConnect(...) call expects a KMP‑friendly context: Any? internally. Pass your Activity; the SDK casts it when talking to the Android layer.

Automatic sync cadence

  • The SDK attempts to sync steps automatically every ~3–4 hours (platform permitting). Actual cadence depends on permissions, device state, and OS scheduling.

Fetch vs. Sync

  • AFHealthTracking.fetchHealthDataByDate(day, month, year) returns raw local device data for diagnostics or reviews. This data is not immediately reflected in Activities.
  • AFCore.smartWalking().syncSteps() pushes data to the server; Activities are updated after server processing. Processing time varies with load/time of day and usually completes in a few minutes.

If you need Activities to reflect new steps, call syncSteps and give the server a short window to process before reloading the UI.


Using AFHealthTracking directly (diagnostics/advanced flows)

AFHealthTracking is a multiplatform façade for permission workflows and local reads. Prefer connect... APIs for “happy path”. Use AFHealthTracking when:

  • You need to re‑request permissions after an uninstall/reinstall or edge recovery, without creating a new SmartWalking profile.
  • You want to verify permissions or read a day’s raw metrics to help a member troubleshoot.

API surface (expect object)

// Kotlin (shared)
internal expect object AFHealthTracking {
@Throws(Throwable::class)
suspend fun requestPermissions(context: Any?): Boolean

@Throws(Throwable::class)
suspend fun hasPermissions(context: Any?): Boolean

@Throws(Throwable::class)
suspend fun revokePermissions(): Boolean

@Throws(Throwable::class)
suspend fun fetchHealthDataByDate(day: Int, month: Int, year: Int): HealthData
}

Request or check permissions

Android (Kotlin)

// Pass the current Activity. It is typed as Any? to satisfy KMP boundaries.
val granted = withContext(Dispatchers.Main) {
AFHealthTracking.requestPermissions(context = thisActivity)
}
val hasAll = withContext(Dispatchers.IO) { AFHealthTracking.hasPermissions(thisActivity) }

iOS (Swift)

// Context may be unused on iOS. The call still conforms to the shared API.
let granted = try await AFHealthTracking.requestPermissions(context: nil)
let hasAll = try await AFHealthTracking.hasPermissions(context: nil)

Read a day of local health data (diagnostics)

Android (Kotlin)

val local = withContext(Dispatchers.IO) {
AFHealthTracking.fetchHealthDataByDate(day = 20, month = 8, year = 2025)
}
// Render local-only preview for support; not yet in Activities.

iOS (Swift)

let local = try await AFHealthTracking.fetchHealthDataByDate(day: 20, month: 8, year: 2025)
// Display the local summary to help the user verify device records.

Revoke permissions

val revoked = withContext(Dispatchers.IO) { AFHealthTracking.revokePermissions() }
let revoked = try await AFHealthTracking.revokePermissions()

Example flows

First‑run connect

  1. Show a pre‑permission screen explaining why health access is needed.
  2. Call the connect API for the platform:
    • connectAppleHealthKit(deviceId:) (iOS)
    • connectGoogleHealthConnect(activity, deviceId) (Android)
  3. On success, show Last synced and a Sync now button.

Edge recovery (reinstall or permissions lost)

  1. Detect missing permissions with AFHealthTracking.hasPermissions(...).
  2. If missing and you do not want to create a new SmartWalking profile, call AFHealthTracking.requestPermissions(...) directly.
  3. Once granted, call syncSteps to push the recent window.

Manual sync

val pushed = withContext(Dispatchers.IO) { AFCore.smartWalking().syncSteps() }
// Wait a short window; then refresh Activities.
let pushed = try await AFCore.shared.smartWalking().syncSteps()
// After server processing, reload Activities UI.

UI patterns & tips

  • Present the why before the OS permission dialogs to maximize grant rates.
  • Show Last synced timestamps and a Sync action; schedule background syncs as hints, not guarantees.
  • If the OS background constraints are tight (battery saver, no network), surface a clear status to the user.
  • Handle Health Connect not installed by deep‑linking to the Play Store; on iOS, handle Health unavailability gracefully.
  • Keep the Activities screen timezone‑aware when you refresh after sync.

Error handling

  • Treat permission denials as soft failures. Offer education and a retry.
  • Network and server processing delays should display actionable status (“Sync queued. We’ll update your activity shortly.”).
  • Wrap calls with try/catch and map exceptions to friendly messages. Log details when enableLogging(true) is on.

Quick reference

// iOS
try await AFCore.shared.smartWalking().connectAppleHealthKit(deviceId: vendorId)
try await AFCore.shared.smartWalking().syncSteps()
let granted = try await AFHealthTracking.requestPermissions(context: nil)
let hasAll = try await AFHealthTracking.hasPermissions(context: nil)
let local = try await AFHealthTracking.fetchHealthDataByDate(day: 20, month: 8, year: 2025)
// Android
val ok = AFCore.smartWalking().connectGoogleHealthConnect(activity, deviceId)
val pushed = AFCore.smartWalking().syncSteps()
val granted = AFHealthTracking.requestPermissions(context = activity) // KMP Any?; cast internally
val hasAll = AFHealthTracking.hasPermissions(activity)
val local = AFHealthTracking.fetchHealthDataByDate(day = 20, month = 8, year = 2025)