Facilities
The Facilities feature manages all locations associated with the logged-in member, including gyms, partner sites, and other supported venues. It provides the ability to retrieve a member’s facilities, update their verification status, remove unwanted locations, enable geofence-based presence monitoring, and submit new facility nominations.
What you can build
- Facility list screens: load and display the member’s current facilities.
- Verification flows: allow members to confirm or reject a facility association.
- Management tools: let members remove outdated or incorrect facilities.
- Presence monitoring experiences: leverage proximity/geofencing to detect visits.
- Facility nomination flows: let members submit new facility nominations and review their past submissions.
Load the member’s facilities
Retrieve the list of facilities currently associated with the logged-in member. This includes verified, pending, and blacklisted locations, as returned directly from the backend. Use this operation to populate facility lists, onboarding experiences, or any flow that depends on showing a member’s current facilities.
Android (Kotlin)
lifecycleScope.launch {
try {
val facilities = AFCore.facilities().get()
facilitiesRecycler.submitList(facilities.filterNotNull())
} catch (t: Throwable) {
showError("Could not load facilities: ${t.message}")
}
}
iOS (Swift)
Task {
do {
let facilities = try await AFCore.shared.facilities().get()
self.updateUI(with: facilities.compactMap { $0 })
} catch {
self.showError("Could not load facilities: \\(error.localizedDescription)")
}
}
Update a facility
Update the verification status of a facility already associated with the member or discovered through Map(). This operation allows you to mark a facility as verified (trusted) or blacklisted (rejected). If the facility originates from a Map-based discovery flow, it can still be updated even if it has not previously appeared in the member’s facility list.
Use this when the member confirms a location, disputes an incorrect match, or interacts with a facility surfaced by Map() results.
Android (Kotlin)
lifecycleScope.launch {
val result = AFCore.facilities().update(facilityId = 12345, verified = true)
if (result.status) refreshFacilitiesUI() else showError("Update failed: ${result.statusMessage}")
}
iOS (Swift)
Task {
do {
let result = try await AFCore.shared.facilities().update(facilityId: 12345, verified: true)
result.status ? self.refreshFacilitiesUI() : self.showError("Update failed: \\(result.statusMessage ?? "unknown")")
} catch {
self.showError("Update failed: \\(error.localizedDescription)")
}
}
Delete a facility
Remove a facility from the member’s list when it is no longer relevant or was added incorrectly. This operation permanently detaches the facility from the member’s account. Use this in management screens, settings pages, or corrective flows where the member needs to clear outdated or inaccurate facility associations.
Android (Kotlin)
lifecycleScope.launch {
val result = AFCore.facilities().delete(facilityId = 12345)
if (result.status) removeItemFromUI(12345) else showError("Delete failed: ${result.statusMessage}")
}
iOS (Swift)
Task {
do {
let result = try await AFCore.shared.facilities().delete(facilityId: 12345)
result.status ? self.removeItemFromUI(12345) : self.showError("Delete failed: \\(result.statusMessage ?? "unknown")")
} catch {
self.showError("Delete failed: \\(error.localizedDescription)")
}
}
Nominate a Facility
Allow members to submit a new facility that is not yet part of the network. This is useful when a member visits a gym or location that isn’t currently recognized by the system and wants it considered for inclusion. A nomination captures key details such as address, contact information, and whether the member is currently associated with that facility. Once submitted, the nomination can be reviewed and its status checked later through the member’s list of nominations.
Notes & best practices
- Required fields:
- clubName
- address1
- city
- stateCode
- zipCode
- Optional fields:
- address2
- latitude
- longitude
- phone
- Number
- managerName
- currentlyAMember
- metadata.
- Metadata keys: Useful keys include:
- website — canonical website URL
- email — contact email (also passed as top-level param when available)
- googlePlaceId — Google Places ID for programmatic verification
- yelpId, foursquareId — external provider IDs
- source — origin of nomination (e.g., ios-app, android-app, web)
- IDs vs free text: When possible, include provider IDs (Google/Yelp/Foursquare) — they make backend verification and de-duplication far easier than free-text matches.
- Privacy & validation: Avoid sending PII unnecessarily. If you include emails/phone numbers, ensure your privacy policy covers it. Validate email/phone formats client-side if you can.
- Optimistic UI: You may show a temporary “nomination pending” state in the UI and update it once the API confirms the request.
Android (Kotlin)
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 = "manager@downtownfitness.example",
managerName = "Jane Doe",
currentlyAMember = true,
metadata = mapOf(
// Useful external identifiers / metadata for verification
"website" to "https://www.downtownfitness.example",
"googlePlaceId" to "ChIJN1t_tDeuEmsRUsoyG83frY4",
"yelpId" to "downtown-fitness-springfield",
"foursquareId" to "4b0587aef964a52012ad22e3",
"externalPartnerId" to "partner-12345",
"source" to "mobile-app"
)
)
if (result.status) {
// show success UI
showToast("Nomination submitted")
} else {
// show domain-friendly message returned by backend
showError("Nomination failed: ${result.statusMessage ?: "unknown error"}")
}
} catch (t: Throwable) {
// network / unexpected error
showError("Could not submit nomination: ${t.message}")
}
}
iOS (Swift)
Task {
do {
let metadata: [String: String] = [
// Useful external identifiers / metadata for verification
"website": "https://www.downtownfitness.example",
"googlePlaceId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"yelpId": "downtown-fitness-springfield",
"foursquareId": "4b0587aef964a52012ad22e3",
"externalPartnerId": "partner-12345",
"source": "ios-app"
]
let result = try await AFCore.shared.facilities().nominateFacility(
clubName: "Downtown Fitness",
address1: "123 Main St",
address2: nil,
city: "Springfield",
stateCode: "CA",
zipCode: "90210",
latitude: 34.052235,
longitude: -118.243683,
phoneNumber: "(555) 123-4567",
email: "manager@downtownfitness.example",
managerName: "Jane Doe",
currentlyAMember: true,
metadata: metadata
)
if result.status {
// success UI
showToast("Nomination submitted")
} else {
// server-side domain message
showError("Nomination failed: \(result.statusMessage ?? "unknown error")")
}
} catch {
// network / unexpected error
showError("Could not submit nomination: \(error.localizedDescription)")
}
}
Geofence Monitoring
Enable or disable geofence-based monitoring for the member’s facilities. When activated, the Facilities module integrates internally with AFGeofencing to track the member’s proximity to registered locations. This allows the system to detect presence, trigger check-in logic, or support location-based engagement features depending on the tenant’s configuration.
Use geofence monitoring during login, onboarding, or whenever presence detection is required. Stop monitoring when the member logs out or no longer needs background location tracking.
Android (Kotlin)
lifecycleScope.launch {
try {
// Start monitoring geofences
val started = AFCore.facilities().startMonitoring()
if (started) {
val active = AFCore.facilities().isMonitoringActive()
println("Monitoring active: $active")
}
// Stop monitoring geofences
val stopped = AFCore.facilities().stopMonitoring()
if (stopped) {
val active = AFCore.facilities().isMonitoringActive()
println("Monitoring active: $active")
}
} catch (t: Throwable) {
Log.e("Facilities", "Monitoring error: ${t.message}")
}
}
iOS (Swift)
Task {
do {
// Start monitoring geofences
let started = try await AFCore.shared.facilities().startMonitoring()
if started {
let active = try await AFCore.shared.facilities().isMonitoringActive()
print("Monitoring active: \(active)")
}
// Stop monitoring geofences
let stopped = try await AFCore.shared.facilities().stopMonitoring()
if stopped {
let active = try await AFCore.shared.facilities().isMonitoringActive()
print("Monitoring active: \(active)")
}
} catch {
print("Monitoring error: \(error.localizedDescription)")
}
}
UI patterns & tips
- Optimistic UI: update locally first, refresh afterward.
- Blacklisted facilities: highlight clearly or hide depending on product.
- Auto-add behavior: calling
updateon an unknown id automatically inserts it. - Sync strategy: cache locally and refresh only as needed.
Error handling
- Network/server issues throw
ThrowableorError. - Use friendly messages:
- “We couldn’t load your facilities. Pull to retry.”
- “Update failed. Check your connection and try again.”
Document updated: 2025-11-19