Skip to main content

SmartWalking

SmartWalking is the step-tracking entitlement. It connects the member's device health platform -- Apple HealthKit on iOS or Google Health Connect on Android -- to the ActiveFit+ backend. The SDK collects steps, calories burned, distance traveled (miles), and active minutes, then submits them for goal validation.

Each day the member meets their daily step goal counts as one qualifying activity toward their monthly target.


How to Configure SmartWalking

SmartWalking follows a simple setup flow: check the member's profile, connect if needed, then let the SDK handle background syncing.

Connection Flow

Show a connect screen when the member is not yet linked, then transition to the step dashboard after a successful connection.

SmartWalking connection flow showing the before state (connect button) transitioning to the after state (step dashboard with progress ring)

Step Progress Dashboard

Once connected, display a circular progress ring for daily steps, health metrics, and a manual sync button.

Step progress ring showing iOS (Apple HIG) and Android (Material Design 3) implementations with daily step count, distance, calories, and active minutes

Key data mappings:

UI ElementAFCore Source
Step countsyncSteps() result, then refresh via activities().get()
Daily goalgetDailyStepGoal() (default 10,000)
Distance, calories, active minutesactivities().get()ActivityMetaData on SmartWalking activities
Connection statusgetRegisteredProfile()?.isConnected
Sync state indicatorsyncEvents StateFlow (SmartWalkingEvent)

Step 1 -- Check the Profile

Before showing any SmartWalking UI, check whether the member is already connected.

lifecycleScope.launch {
val profile = AFCore.smartWalking().getRegisteredProfile()

if (profile != null && profile.isConnected) {
// Already connected — show step dashboard, enable manual sync button
showStepDashboard()
} else {
// Not connected — show a "Connect" button
showConnectButton()
}
}

getRegisteredProfile() fetches the member's SmartWalking registration from the server. It returns nil/null if the member has never connected, or a SmartWalkingProfile with isConnected = true if they are actively registered.


Step 2 -- Connect

When the member taps the connect button, call the platform-specific connect method. This single call handles everything:

  1. Prompts the native health permission sheet (HealthKit on iOS, Health Connect on Android).
  2. Registers the device with the ActiveFit+ backend.
  3. Enables auto-sync automatically on success.
connectButton.setOnClickListener {
lifecycleScope.launch {
try {
val profile = AFCore.smartWalking().connectGoogleHealthConnect(
context = this@MainActivity,
deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
)

if (profile.isConnected) {
showSuccess("Health Connect connected — syncing will start automatically")
showStepDashboard()
} else {
showMessage("Health Connect permissions were denied")
}
} catch (e: Exception) {
showError("Could not connect: ${e.message}")
}
}
}

Health Connect must be installed on Android. If the device does not have Health Connect, direct the member to the Play Store before calling connectGoogleHealthConnect.

After a successful connection, auto-sync is enabled automatically -- you do not need to call enableAutoSync() separately. The SDK will begin syncing step data in the background approximately every hour.


Step 3 -- Sync

Once connected, the SDK handles data synchronization through two mechanisms:

Automatic background sync runs approximately every hour without any member interaction. The SDK reads health data from the device, determines which dates need syncing, and submits the data to the server.

Manual sync lets the member request an immediate refresh. This is useful when the member wants to see updated step counts without waiting for the next background cycle.

syncButton.setOnClickListener {
lifecycleScope.launch {
val result = AFCore.smartWalking().syncSteps()
if (result.status) {
showSuccess("Steps synced successfully")
// Refresh the Activities screen after a brief delay
} else {
showMessage(result.statusMessage ?: "Nothing to sync")
}
}
}

syncSteps() has a built-in concurrency guard. If a sync is already running, it returns immediately with status = false and "Sync already in progress" -- it does not queue.


Complete Integration Example

Putting it all together -- a typical SmartWalking screen that checks the profile, shows the right UI, and lets the member connect or sync:

class SmartWalkingFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadSmartWalkingState()
}

private fun loadSmartWalkingState() {
lifecycleScope.launch {
val profile = AFCore.smartWalking().getRegisteredProfile()

if (profile != null && profile.isConnected) {
// Connected — show dashboard with sync button
binding.connectSection.isVisible = false
binding.dashboardSection.isVisible = true

val goal = AFCore.smartWalking().getDailyStepGoal()
binding.stepGoalLabel.text = "Daily goal: $goal steps"
} else {
// Not connected — show connect button
binding.connectSection.isVisible = true
binding.dashboardSection.isVisible = false
}
}
}

private fun onConnectClicked() {
lifecycleScope.launch {
try {
val profile = AFCore.smartWalking().connectGoogleHealthConnect(
context = requireActivity(),
deviceId = Settings.Secure.getString(
requireContext().contentResolver,
Settings.Secure.ANDROID_ID
)
)

if (profile.isConnected) {
showSuccess("Connected — auto-sync enabled")
loadSmartWalkingState() // Refresh UI
} else {
showMessage("Permissions denied")
}
} catch (e: Exception) {
showError("Connection failed: ${e.message}")
}
}
}

private fun onSyncClicked() {
lifecycleScope.launch {
val result = AFCore.smartWalking().syncSteps()
if (result.status) {
showSuccess("Steps synced")
} else {
showMessage(result.statusMessage ?: "Nothing to sync")
}
}
}
}

Automatic Background Sync

After connection, auto-sync runs in the background with no further setup needed. Here is how it behaves on each platform:

AndroidiOS
MechanismWorkManager periodic task (~1 hour)HealthKit background delivery + BGAppRefreshTask
Survives app killYesYes
Survives rebootYesYes
Network requiredYes (waits for connectivity)No (syncs immediately, retries on failure)
Entitlement checkYes (cancels if SmartWalking removed)Yes (cancels if SmartWalking removed)

Consumer App Requirements

Android

No additional setup is required. WorkManager and the boot config store are fully managed by the SDK.

iOS

No additional setup is required beyond calling AFCore.initialize(). The SDK automatically restores auto-sync on each app launch if it was previously enabled.

Enable the required Xcode capabilities:

  • HealthKit -- required for step data access.
  • Background Modes > Background fetch -- required for HealthKit background delivery.

Manual Auto-Sync Control

Auto-sync is enabled automatically when the member connects. In most cases, you do not need to manage it directly. However, if you need to offer a toggle:

// Android — disable
AFCore.smartWalking().disableAutoSync()

// Android — re-enable
AFCore.smartWalking().enableAutoSync()

Entitlement Check

Each background sync cycle verifies that the member still has the SmartWalking entitlement. If the entitlement is no longer present (e.g., the member's plan changed), the SDK cancels the periodic sync and logs the cancellation. This prevents unnecessary network calls and battery usage.


Sync Events

Observe the syncEvents flow to show a progress indicator while syncing:

lifecycleScope.launch {
AFCore.smartWalking().syncEvents.collect { event ->
binding.syncIndicator.isVisible = (event is SmartWalkingEvent.SyncStarted)
}
}

Subscribe to Events

Monitor sync lifecycle events in real time.

Event Types

EventDescription
SyncStartedSync has begun.
SyncCompleted(result)Sync finished. Check result.status for success.
SyncFailed(message, cause)Sync encountered an error.
BackgroundDeliveryTriggeredSystem woke the app for background sync (iOS only).
AutoSyncEnabledAuto-sync was turned on.
AutoSyncDisabledAuto-sync was turned off.
val subscription = AFCore.smartWalking().subscribeToEvents(
onEvent = { event ->
when (event) {
is SmartWalkingEvent.SyncStarted ->
Log.d("SW", "Sync started")
is SmartWalkingEvent.SyncCompleted ->
Log.d("SW", "Sync completed: ${event.result.status}")
is SmartWalkingEvent.SyncFailed ->
Log.e("SW", "Sync failed: ${event.message}")
else ->
Log.d("SW", "Event: $event")
}
},
onError = { error ->
Log.e("SW", "Event stream error: ${error.message}")
}
)

// When no longer needed:
subscription.close()

Diagnostics

Retrieve a snapshot of the SmartWalking module's state for troubleshooting.

FieldTypeDescription
profileSmartWalkingProfile?Server registration (device, type, timestamp).
isAutoSyncEnabledBooleanWhether background sync is active.
permissionStatusHealthPermissionStatus?Current health data permission state.
stepDataSourcesList<HealthKitDataSource>Apps contributing step data (iOS only).
deviceTypeDeviceType?Registered device type (IOS, HEALTH_CONNECT).
lastSyncTimestampString?ISO-8601 timestamp of the last successful sync.
// Android
val diagnostics = AFCore.smartWalking().getDiagnostics()
Log.d("SW", "Connected: ${diagnostics.profile?.isConnected}")
Log.d("SW", "Auto-sync: ${diagnostics.isAutoSyncEnabled}")
Log.d("SW", "Last sync: ${diagnostics.lastSyncTimestamp ?: "never"}")

Step Data Sources (iOS)

On iOS, multiple apps can write step data to HealthKit. Use this to show the member which sources are contributing:

let sources = try await AFCore.shared.smartWalking().getStepDataSources()
for source in sources {
print("\(source.name) (\(source.bundleId ?? "unknown"))")
}

On Android, Health Connect does not expose per-source queries. This method returns an empty list.


Dates Pending Sync

Check how many days have unsynced health data:

// Android
val dates = AFCore.smartWalking().getDatesToSync()
Log.d("SW", "${dates.size} days pending sync")

Daily Step Goal

Retrieve the member's configured daily step goal from their program settings:

// Android
val goal = AFCore.smartWalking().getDailyStepGoal()
binding.stepProgressBar.max = goal

The default is 10,000 steps if the program configuration is unavailable.


Disconnect

To disconnect the member's SmartWalking registration entirely:

// Android
AFCore.smartWalking().unregisterUser()

This disables auto-sync, notifies the server, and clears all local SmartWalking state. The member's historical data on the server is preserved. After disconnecting, the member must go through the connect flow again.


Limitations

  • iOS background delivery is opportunistic. The system may delay sync tasks when the device is locked, in Low Power Mode, or under heavy load.
  • Android WorkManager runs approximately hourly. Actual cadence depends on battery state, network availability, and OEM optimizations (Doze mode).
  • Health Connect must be installed on Android. Devices without it cannot use SmartWalking.
  • Step data is not real-time. There is a processing delay between syncSteps() and activities appearing in the member's history. Show a "Sync complete" message rather than immediately refreshing activities.
  • Battery optimization (Android). Some OEMs (Samsung, Xiaomi, Huawei) aggressively kill background processes. Guide members to exempt the app from battery optimization if sync is unreliable.

Troubleshooting

SymptomLikely CauseSolution
Steps not syncing in backgroundAuto-sync not enabledVerify isAutoSyncEnabled is true. If the member connected before auto-sync was automatic, call enableAutoSync() explicitly.
Background sync unreliable (Android)OEM battery optimizationGuide the member to exempt the app from battery optimization in device settings.
Sync stops after plan changeEntitlement removedExpected. The SDK cancels background sync when SmartWalking is no longer in the member's program. Re-enable after the entitlement is restored.
"AFCore not initialized" in logsFirst launch, no persisted configNormal on the very first launch. Once AFCore.initialize() runs, the config is persisted for future background syncs.
syncSteps() returns "Sync already in progress"Concurrent sync attemptWait for the current sync to finish. Only one sync runs at a time.
No step data after connectingHealth permissions deniedCheck that the member granted health data permissions. On Android, verify Health Connect is installed.

Quick Reference

// Android
AFCore.smartWalking().getRegisteredProfile()
AFCore.smartWalking().connectGoogleHealthConnect(context, deviceId)
AFCore.smartWalking().syncSteps()
AFCore.smartWalking().syncEvents // StateFlow<SmartWalkingEvent>
AFCore.smartWalking().enableAutoSync()
AFCore.smartWalking().disableAutoSync()
AFCore.smartWalking().isAutoSyncEnabled
AFCore.smartWalking().subscribeToEvents(onEvent, onError)
AFCore.smartWalking().getDiagnostics()
AFCore.smartWalking().getDatesToSync()
AFCore.smartWalking().getDailyStepGoal()
AFCore.smartWalking().getStepDataSources()
AFCore.smartWalking().unregisterUser()