Swan SDK
Guides

Location

Tag profile updates and events with lat / lng — and the design choice that means the SDK never asks for location permission itself.

The Swan SDK does not acquire device location itself. The host app supplies coordinates via Swan.updateLocation(...) (on Android / iOS / Web) or Swan.updateLocation() (on React Native, which integrates with react-native's Geolocation under the hood).

This guide explains why and how.


1. Why the host app owns acquisition

If the SDK acquired location, it would:

  • Force every host app to depend on the platform location framework (Google Play Services Location, Core Location, navigator.geolocation) — including apps that don't need location at all
  • Force every host app to declare the corresponding manifest / Info.plist permission strings — which causes App Store reviewer flags and lower opt-in rates
  • Create ambiguity about which permission prompt the user is responding to — the host's "we use your location for nearby stores" or the SDK's?

By making the host app supply coordinates, the SDK stays small (no location-framework dependency) and the host app keeps full control over when and why it asks for permission.

The SDK reads SwanConfig.location.enabled to surface a documented opt-in flag — useful for host apps that want a single switch to gate their own acquisition logic.


2. Enable in config

import cx.swan.sdk.Swan
import cx.swan.sdk.SwanConfig
import cx.swan.sdk.LocationConfig

Swan.init(
    applicationContext,
    appId = "your-app-id",
    config = SwanConfig(
        location = LocationConfig(enabled = true),
    ),
)
Swan.shared.initialize(
    appId: "your-app-id",
    config: SwanConfig(location: .init(enabled: true))
)
import SwanSDK from '@loyalytics/swan-react-native-sdk';

SwanSDK.getInstance('your-app-id', {
    isProduction: true,
    location: { enabled: true },
});
Swan.init({
    appId: 'your-app-id',
    location: { enabled: true },
});

3. Push coordinates

// Without accuracy
Swan.updateLocation(latitude = 25.2048, longitude = 55.2708)

// With accuracy in meters
Swan.updateLocation(
    latitude = 25.2048,
    longitude = 55.2708,
    accuracy = 12.0,
)

Coordinates are WGS-84 decimal degrees (the standard everywhere). Accuracy is in meters at 68% confidence (matches Location.getAccuracy() on Google Play Services).

The SDK timestamps the call (server-side timestamp on a recent SDK clock) and persists it as part of the device blob — surfaced via Swan.getDeviceInfo().location.

Wiring to FusedLocationProvider

A typical Google Play Services setup:

val fused = LocationServices.getFusedLocationProviderClient(this)
fused.lastLocation.addOnSuccessListener { loc ->
    loc?.let { Swan.updateLocation(it.latitude, it.longitude, it.accuracy.toDouble()) }
}
import CoreLocation

let mgr = CLLocationManager()
mgr.requestWhenInUseAuthorization()

class Delegate: NSObject, CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager,
                         didUpdateLocations locations: [CLLocation]) {
        let loc = locations.last!
        Swan.shared.updateLocation(
            latitude: loc.coordinate.latitude,
            longitude: loc.coordinate.longitude,
            accuracy: loc.horizontalAccuracy
        )
    }
}

You don't pass coordinates to the RN SDK — it acquires them internally via the platform's geolocation API after you've ensured the host app has been granted location permission.

import SwanSDK from '@loyalytics/swan-react-native-sdk';

const sdk = SwanSDK.getCurrentInstance();
// Make sure permission is granted (host-app concern), then:
await sdk?.updateLocation();

If location is disabled in config (location.enabled: false), updateLocation() no-ops with a warning.

navigator.geolocation.getCurrentPosition((pos) => {
    Swan.updateLocation({
        latitude: pos.coords.latitude,
        longitude: pos.coords.longitude,
        accuracy: pos.coords.accuracy,
    });
});

4. Reading the stored location

val info = Swan.getDeviceInfo()
val loc  = info.location  // SwanLocation? — null until first updateLocation

loc?.let {
    Log.d("loc", "lat=${it.latitude}, lng=${it.longitude}, t=${it.timestamp}")
}

SwanLocation shape: { latitude: Double, longitude: Double, accuracy: Double?, timestamp: Long }.

let loc = Swan.shared.getDeviceInfo().location
import SwanSDK from '@loyalytics/swan-react-native-sdk';

const sdk = SwanSDK.getCurrentInstance();
const info = await sdk?.getDeviceInfo();
// { deviceId, generatedCDID, currentCDID, identifier?, ... }

The RN SDK persists location server-side as part of the device blob. getDeviceInfo() returns the credentials blob — identifiers and registration timestamps. Location flows server-side with the next event ingest, where the campaigns engine reads it for targeting.

const info = await Swan.getDeviceInfo();
const loc  = info.location;

5. Gate your acquisition on isLocationEnabled

If you build your own "should I prompt for location?" logic, branch on Swan.isLocationEnabled() to respect the SDK's config opt-in:

if (Swan.isLocationEnabled()) {
    // Run your fused-location flow; eventually call Swan.updateLocation(...)
}
if Swan.shared.isLocationEnabled() { /* prompt + acquire */ }
import SwanSDK from '@loyalytics/swan-react-native-sdk';

const sdk = SwanSDK.getCurrentInstance();
if (sdk?.isLocationEnabled()) {
    // your prompt + acquisition flow
    await sdk?.updateLocation();
}
if (Swan.isLocationEnabled()) { /* prompt + acquire */ }

6. Usage patterns

PatternCall frequencyWhy
On every app foregroundeach timelow-cost when nothing changed — the SDK persists locally and only ships with the next batch
On profile enrichment momentsat sign-in / first checkouttag the profile with shipping locale for segmentation
On geofence-tripped eventon enter / exithost app owns the geofence framework; SDK just tags the moment
On screen view for store-locatorper screen viewuseful for "show offer based on closest branch" campaigns

Avoid:

  • Continuous location streaming — the SDK isn't designed for trail-style tracking. Push the latest sample on meaningful events instead.
  • Calling without permission — on Android 14+ / iOS Always, supplying background-fetched coords without explicit permission is grounds for store removal.

7. Privacy / compliance notes

The SDK persists the most recent location only — there's no history buffer. The location is part of the device blob and is sent on subsequent event ingest as a device attribute, not a separate event.

If your app is subject to GDPR / CCPA / similar:

  • Treat enabled = true as a user-consented state — flip it off when the user revokes
  • Clear by calling Swan.updateLocation(0.0, 0.0) is not a clear; there is no public clear API in v1. If you need explicit deletion, call Swan.logout() to rotate the device or wait for the next major release
  • The Swan backend honors deletion-of-personal-data requests via the customer ID — not via the SDK

Reference

On this page