Skip to content

Migrating from Transistorsoft

BGeo’s API is intentionally shaped like Transistorsoft’s commercial TSLocationManager: the same method names, the same config keys, the same event vocabulary. A migration is mostly a dependency swap and a licence key, not a redesign.

This guide describes the shape of the move rather than a specific version’s exact imports — substitute whatever your current dependency and class names are.

1. Swap the dependency

Remove the TSLocationManager pod or package, and add:

.package(url: "https://github.com/dc-bgeo/ios-background-geolocation", from: "0.1.0")

SwiftPM only — there is no podspec. Note the Xcode 26.6 floor (why); if your team is pinned to an older Xcode, plan that first, because nothing else will build.

2. Adapt to async/await

Transistorsoft’s iOS API is delegate- and callback-based; BGeo’s is async/await on a @MainActor type:

// Before, roughly
BackgroundGeolocation.ready(config) { state in
if !state.enabled { BackgroundGeolocation.start() }
}
// After
Task {
let state = try await BackgroundGeolocation.ready(config)
if !state.enabled { try await BackgroundGeolocation.start() }
}

Every event also has a callback form (onLocation { } returning a Subscription) if you would rather not restructure every call site at once — see Events.

3. Swap the licence key

Both SDKs read the licence from Info.plist at launch, so the shape is familiar — only the key name changes, and BGeo verifies offline, with no call home:

<!-- Before (Transistorsoft) -->
<key>TSLocationManagerLicense</key>
<string>YOUR_TRANSISTOR_LICENSE</string>
<!-- After (BGeo) -->
<key>BGeoLicense</key>
<string>BGEO1....YOUR_KEY</string>

BGeo binds a production key to your bundle id + Team ID — see License keys.

4. Keep your Info.plist keys

The four usage-description and background-mode keys are the platform’s, not the SDK’s, so whatever you had for TSLocationManager carries over unchanged. See Installation if you need the list.

Config and method compatibility

Most Transistor config keys and methods behave identically — see the Config reference and Methods reference for exact Swift types and defaults. What maps directly (unchanged names and shapes, adapted only to the async/await idiom):

  • Lifecycle: ready, setConfig, start, stop, getState, changePace
  • Positioning: getCurrentPosition, watchPosition, stopWatchPosition
  • Events: onLocation, onMotionChange, onHeartbeat, onProviderChange, onHttp, onConnectivityChange, onGeofence, onGeofencesChange — each returns a Subscription, and each also exists as an AsyncStream
  • Permissions: requestPermission, requestTemporaryFullAccuracy, getProviderState, isPowerSaveMode/onPowerSaveChange
  • Odometer: getOdometer, setOdometer, resetOdometer
  • Upload queue: sync, getLocations, destroyLocations, getCount, destroyLocation, insertLocation
  • Geofences: addGeofence, addGeofences, removeGeofence, removeGeofences, getGeofences, geofenceExists — see the Geofencing guide
  • Logging: logger.error/warn/info/debug/verbose, getLog, destroyLog, uploadLog — see the Logging & debugging guide
  • HTTP config: url, method, headers, params, extras, httpRootProperty, autoSync, autoSyncThreshold, disableAutoSyncOnCellular, batchSync, maxBatchSize, maxRecordsToPersist
  • Motion/filter config: distanceFilter, stopTimeout, stationaryRadius, heartbeatInterval, desiredAccuracy, locationFilterPolicy, kalmanProfile
  • authorization (JWT refresh, now a typed AuthorizationConfig) — see the HTTP guide’s authorization section
  • The notification sub-keys, now a typed NotificationConfig — Android-only, accepted and ignored here

The location object shape is unchanged (coords, timestamp, isMoving, activity, battery, odometer, uuid, extras) — see Data types. Field names are camelCase Swift properties over the same snake_case wire payload (is_moving on the wire, isMoving on Location), a decoding convenience the raw payload doesn’t need to make since JS already uses whichever casing the wire sends.

A handful of keys are accepted for API compatibility but currently do nothing. Don’t rely on foregroundService or backgroundPermissionRationale, and note that debug only plays sound cues — see Limitations — accepted-but-no-op config keys before you build around any of them.

API parity: what’s not here

BGeo deliberately does not implement the following part of Transistorsoft’s surface — an SDK-level scoping decision independent of which language binding sits on top, so it applies here exactly as it does on any other BGeo binding:

Transistor API / configAlternative in BGeo
schedule / startSchedule / scheduleUseAlarmManagerRun your own scheduler (WorkManager, or an AlarmManager alarm) that calls start()/stop().
locationTemplate / geofenceTemplateShape the upload body with httpRootProperty/params/extras instead of a template string — see Shaping the body.
transistorAuthorizationTokenN/A (Transistorsoft-account specific) — use authorization for your own backend.
Custom notification layout / actions / stringsThe supported Notification fields (title/text/channelId/channelName/smallIcon/color/priority) cover a fixed layout, not custom actions or arbitrary strings.
emailLoguploadLog() to your own logUrl endpoint instead of emailing a log file.
useSignificantChangesOnlyNo equivalent mode — the engine manages its own wake sources; see Tracking lifecycle — wake sources.
stopOnStationary / stopAfterElapsedMinutesCall stop() yourself from onMotionChange/onHeartbeat.
persistModeN/A — all tracked locations persist by default; a one-shot getCurrentPosition() fix opts in via its persist option instead.
timestampFormatN/A — timestamps are always ISO-8601 UTC strings, see Location.timestamp.
locationsOrderDirectionN/A — the persisted queue is always oldest-first, see Data pipeline — persistence.
Burst averaging (rollingWindow/burstWindow/maxBurstDistance), an onLocationFilter-style callback, kalmanDebug/filterDebugUnbuilt — these are unknown keys, silently stored but ignored by the native config dict. The implemented filter surface is locationFilterPolicy, kalmanProfile, and odometerAccuracyThreshold — use those instead of per-fix burst averaging or a filter-decision callback.
reset()Call setConfig() with the values you want restored — see the Config reference for the documented default of each key.
startGeofences() (geofence-only tracking mode)start() already runs geofences alongside location tracking — there’s no separate mode.
getLocations() pagination / SQLQuerygetLocations() returns the full queue, oldest-first, as a List<Location>; filter or paginate it yourself. A large offline queue decodes in one go, so read it off the main thread.
startBackgroundTask() / stopBackgroundTask()N/A.
getDeviceInfo() / getSensors()N/A.

Behavioural differences worth knowing

  • No legacy failure-callback quirk. Some Transistorsoft bindings carry a quirk where supplying a failure callback makes ready()/start() resolve instead of failing. This API has no callback parameters at all: a failed call throws a BGeoError, full stop.
  • sync() semantics are unchanged. It returns a [Location] snapshot of the queue taken before the drain starts, not what remains afterwards.
  • A different background architecture. BGeo keeps tracking alive across kill and reboot with CLLocationUpdate.liveUpdates + CLBackgroundActivitySession + CLServiceSession and a rolling wake region, rather than Transistorsoft’s own implementation — see Boot & killed-app behaviour. The external guarantee is the same or better; the trade-off is that the blue indicator stays visible while tracking.
  • The engine is not the same engine. Tracking, filtering and upload are BGeo’s own implementation with their own defaults. A config tuned against Transistorsoft is a starting point, not a guarantee of identical output — re-check distanceFilter, stopTimeout and stationaryRadius on a real drive before shipping.

Steps

  1. Remove the TSLocationManager dependency; add the Swift package. Confirm the Xcode floor first.
  2. Replace the licence key in Info.plist.
  3. Re-point imports at BackgroundGeolocation.
  4. Wrap ready()/start()/stop() call sites in a Task, or keep the callback forms for events while you migrate.
  5. Drive the route you care about and compare point density before deleting the old integration.