Skip to main content

Facilities

The Facilities module manages the locations associated with the authenticated member -- gyms, partner sites, and other supported venues. Use it to load the member's facility list, verify or remove locations, enable geofence-based visit monitoring, and submit nominations for new facilities.


Data Model

Facility

FieldTypeDescription
facilityIdInt?Unique facility identifier.
nameString?Display name of the facility.
addressString?Street address.
cityString?City.
stateString?Two-letter state code.
zipCodeString?Postal code.
latitudeDouble?Geographic latitude.
longitudeDouble?Geographic longitude.
verifiedBoolean?Whether the member has confirmed this facility.

Get Facilities

Retrieve all facilities currently associated with the member. This includes verified, pending, and blacklisted locations.

lifecycleScope.launch {
try {
val facilities = AFCore.facilities().get()
facilitiesAdapter.submitList(facilities.filterNotNull())
} catch (e: Exception) {
showError("Could not load facilities: ${e.message}")
}
}

Verify or Blacklist a Facility

Update the verification status of a facility. Pass verified: true to confirm a location, or verified: false to blacklist it. This also works for facilities discovered through the Map search -- calling update on a facility not yet in the member's list will add it.

lifecycleScope.launch {
val result = AFCore.facilities().update(facilityId = 12345, verified = true)
if (result.status) {
showSuccess("Facility verified")
refreshFacilities()
} else {
showError("Update failed: ${result.statusMessage}")
}
}

Delete a Facility

Remove a facility from the member's list. This permanently detaches the location from their account.

lifecycleScope.launch {
val result = AFCore.facilities().delete(facilityId = 12345)
if (result.status) {
removeFromList(12345)
} else {
showError("Could not remove facility: ${result.statusMessage}")
}
}

Nominate a Facility

Submit a new facility that is not yet part of the network. Use this when a member visits a gym or location that the system does not recognize.

Parameters

ParameterTypeRequiredDescription
clubNameStringYesName of the facility.
address1StringYesPrimary street address.
address2String?NoSuite, unit, or apartment number.
cityStringYesCity.
stateCodeStringYesTwo-letter state code.
zipCodeStringYesPostal or ZIP code.
latitudeDouble?NoGeographic latitude.
longitudeDouble?NoGeographic longitude.
phoneNumberString?NoContact phone number.
emailString?NoContact email address.
managerNameString?NoFacility manager's name.
currentlyAMemberBooleanNoWhether the member belongs to this facility.
metadataMap<String, String>?NoAdditional key-value pairs (see below).

Metadata Keys

KeyDescription
websiteCanonical website URL.
googlePlaceIdGoogle Places ID for programmatic verification.
yelpIdYelp business identifier.
foursquareIdFoursquare venue identifier.
sourceOrigin of the nomination (e.g., "ios-app", "android-app").

Including provider IDs makes backend verification and deduplication more reliable than free-text matching alone.

lifecycleScope.launch {
try {
val result = AFCore.facilities().nominateFacility(
clubName = "Downtown Fitness",
address1 = "123 Main St",
address2 = null,
city = "Springfield",
stateCode = "CA",
zipCode = "90210",
latitude = 34.052235,
longitude = -118.243683,
phoneNumber = "(555) 123-4567",
email = "info@downtownfitness.example",
managerName = "Jane Doe",
currentlyAMember = true,
metadata = mapOf(
"googlePlaceId" to "ChIJN1t_tDeuEmsRUsoyG83frY4",
"source" to "android-app"
)
)

if (result.status) {
showSuccess("Nomination submitted")
} else {
showError("Nomination failed: ${result.statusMessage}")
}
} catch (e: Exception) {
showError("Could not submit nomination: ${e.message}")
}
}

Geofence Monitoring

Enable or disable geofence-based monitoring for the member's verified facilities. When active, the SDK tracks proximity and detects visits automatically. The dwell threshold (how long the member must stay at a facility for a visit to count) is synced automatically from the application settings during SDK initialization. Use subscribeToEvents() to receive real-time geofence events (see Gym Visits for full details).

Start monitoring after login or onboarding. Stop monitoring on logout or when background location tracking is no longer needed.

Start Monitoring

// Android
lifecycleScope.launch {
val started = AFCore.facilities().startMonitoring()
if (started) {
Log.d("Facilities", "Geofence monitoring active")
}
}

Stop Monitoring

// Android
lifecycleScope.launch {
val stopped = AFCore.facilities().stopMonitoring()
}

Check Monitoring Status

// Android
lifecycleScope.launch {
val active = AFCore.facilities().isMonitoringActive()
}

Location Services & Permissions

Geofence monitoring depends on both the device's system-level Location Services master switch and the consumer app's per-app location authorization. The facilities API exposes helpers to observe geofence events, react to status changes, and route the user to the correct Settings page when something is off.

Observe Geofence Events

geofenceEvents is a SharedFlow<AFGeofenceEvent>. On Android you can collect it directly; on iOS use subscribeToEvents(onEvent:onError:) instead, since a Kotlin SharedFlow does not bridge to a Swift AsyncSequence.

// Android
lifecycleScope.launch {
AFCore.facilities().geofenceEvents.collect { event ->
when (event) {
is AFGeofenceEvent.Entered -> showNotification("Arrived at ${event.geofenceId}")
is AFGeofenceEvent.Exited -> showNotification("Left ${event.geofenceId}")
else -> { }
}
}
}

Subscribe to Status Changes

subscribeToStatus(...) replays the latest AFGeofencingStatus to each new collector and emits whenever the SDK transitions states (e.g. from PAUSED to ACTIVE once the user grants Always location, or to PERMISSIONS_DENIED if it is revoked in Settings). Use it to drive reactive UI without polling. It is not a suspend function. Call close() on the returned subscription to stop.

// Android
val subscription = AFCore.facilities().subscribeToStatus(
onStatus = { status ->
when (status) {
AFGeofencingStatus.PERMISSIONS_DENIED,
AFGeofencingStatus.PERMISSIONS_REDUCED_ACCURACY ->
// Reduced accuracy = location granted but Precise/Fine is off;
// geofencing needs precise accuracy, so route to app settings.
AFCore.facilities().openAppSettings()
AFGeofencingStatus.LOCATION_SERVICES_DISABLED ->
AFCore.facilities().openLocationSettings()
else -> { }
}
},
onError = { error -> /* handle */ }
)

// Later
subscription.close()

Check Location Services & Route to Settings

areLocationServicesEnabled() reports whether the device's system-wide Location Services master switch is on. When it is false, no app receives location data regardless of per-app authorization, and startMonitoring() fails. Call it before any permission flow so you can guide the user to the master switch first via openLocationSettings(). All three helpers are non-suspend.

Use openLocationSettings() for the system-wide master switch (Android ACTION_LOCATION_SOURCE_SETTINGS; iOS opens the app's settings page, since iOS exposes no deep-link to the master switch). Use openAppSettings() when the master switch is on but the app's per-app location permission was denied (PERMISSIONS_DENIED).

// Android
if (!AFCore.facilities().areLocationServicesEnabled()) {
showAlert("Turn on Location Services") {
AFCore.facilities().openLocationSettings()
}
return
}
lifecycleScope.launch {
AFCore.facilities().startMonitoring()
}

Best Practices

  • Cache facilities locally and refresh when the member opens the facilities screen or pulls to refresh.
  • Show a confirmation dialog before deleting a facility, since the operation is permanent.
  • Use optimistic UI updates when verifying or blacklisting -- update the local list immediately and revert if the API call fails.
  • Validate nomination inputs client-side before submitting. Require at minimum: club name, address, city, state, and ZIP code.

Quick Reference

// Android
AFCore.facilities().get()
AFCore.facilities().update(facilityId, verified)
AFCore.facilities().delete(facilityId)
AFCore.facilities().nominateFacility(clubName, address1, address2, city, stateCode, zipCode, ...)
AFCore.facilities().getNominatedFacilities()
AFCore.facilities().startMonitoring()
AFCore.facilities().stopMonitoring()
AFCore.facilities().isMonitoringActive()
AFCore.facilities().subscribeToEvents(onEvent, onError)