Traccar Client SDK
A Kotlin Multiplatform background location tracking SDK for Traccar - and any other server that accepts the same simple HTTP GET protocol. Runs on Android and iOS, persists positions in a local SQLite queue, and uploads them with network-aware retry.
This repository publishes two artifacts:
- Native SDK - Maven Central (
org.traccar:traccar-client-sdk) and an XCFramework distributed via Swift Package Manager.
- Flutter plugin - pub.dev (
traccar_client_sdk).
Install
Android (Gradle)
dependencies {
implementation("org.traccar:traccar-client-sdk:0.0.11")
}
iOS (Swift Package Manager)
.package(url: "https://github.com/traccar/traccar-client-sdk.git", from: "0.0.11")
Flutter
dependencies:
traccar_client_sdk: ^0.0.11
Quick start
Android (Kotlin)
val config = Config(
serverUrl = "https://demo.traccar.org",
deviceId = "123456",
)
Tracker.shared(activity).start(activity, config)
Tracker.shared(context).stop(context)
start requires a ComponentActivity because it launches a transparent permission activity if location, notification, or activity-recognition permissions are not yet granted.
iOS (Swift)
import TraccarClientSDK
let config = Config(
serverUrl: "https://demo.traccar.org",
deviceId: "123456"
)
Tracker.shared.start(config: config)
Tracker.shared.resume()
Tracker.resume() reloads the saved config and restarts the engine if iOS launches the app in the background in response to a significant location change or region exit. Without it, those background wakes are silent.
Flutter
import 'package:traccar_client_sdk/traccar_client_sdk.dart';
final tracker = TraccarClientSdk();
await tracker.start(Config(
serverUrl: 'https://demo.traccar.org',
deviceId: '123456',
));
// later
await tracker.stop();
Configuration
Config
LocationConfig
Accuracy.HIGHEST is a special mode: it overrides distanceMeters = 0, intervalSeconds = 0, and stopDetection = false, requesting the maximum-rate stream from the OS. Use it for navigation-style scenarios where battery is not a concern.
NotificationConfig (Android)
| Field | Type | Default | Description |
|---|
text | String | "Location tracking" |
API
How it works
The pipeline is the same on both platforms:
PositionProvider → LocationFilter → TrackerEngine → PositionQueue → HttpUploader → server
Filters and OS request shape (Android)
LocationFilter is OR (any trigger accepts). The OS request is single-criterion to avoid the AND deadlock that produces silent "stationary forever" behavior: if distanceMeters > 0, the OS is asked to deliver on distance only; otherwise it delivers on time only. Accuracy.HIGHEST zeroes both and requests the max rate.
Stop detection
Both platforms use the OS's activity recognition to pause GPS when the user is sitting still:
- Android -
ActivityRecognitionClient.requestActivityTransitionUpdates (transitions) and a one-shot snapshot at start so that already-stationary devices are correctly classified rather than waiting for a transition that never fires.
Fast first fix
To avoid a silent initial period when the configured interval is large, both Android providers issue a one-shot getCurrentLocation alongside the periodic stream. iOS does not need this - startUpdatingLocation delivers within seconds.
Persistence and recovery
ConfigStore - persists the active config so background-launched services know what to do.
PositionQueue - persists positions across restarts.
LogStore - persists the diagnostic log retrievable via getLogs.
Both platforms hold a small, self-contained SQLite database (tracker.db).
Reliability
Android
- Foreground service (
TrackerService) with FOREGROUND_SERVICE_TYPE_LOCATION keeps the process alive and visible to the user.
START_REDELIVER_INTENT restarts the service with the original intent if the OS kills it (e.g., memory pressure).
Realistic limits: aggressive OEM task killers (Xiaomi/Huawei) can ignore Android's contract; the user can force-stop the app from settings; the restricted App Standby Bucket disables most background work. These are platform limitations, not bugs.
iOS
- Significant Location Changes (
startMonitoringSignificantLocationChanges) - the key API that wakes the app from a terminated state on roughly ~500m shifts.
- Region monitoring - when the SDK detects the user is stationary it registers a
CLCircularRegion around the spot so iOS wakes the app on exit.
Tracker.resume() - must be called from (or equivalent) so SLC / region wakes actually restart the engine.
Realistic limits: user-initiated force-quit from the App Switcher disables SLC until the user reopens the app; phone reboot requires the user to open the app once before tracking resumes (iOS has no BootReceiver equivalent); Low Power Mode can reduce wake frequency.
Permissions
Android (in your AndroidManifest.xml)
The SDK manifest already declares:
ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION
Runtime permission prompts (location, notifications, activity recognition, background location) are launched automatically by start. On first start, the SDK also opens the Ignore Battery Optimization settings screen once.
iOS (in your Info.plist)
Add both:
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationWhenInUseUsageDescription
NSMotionUsageDescription (for activity-based stop detection)
And enable the Location updates background mode in your target capabilities.
If you use heartbeatIntervalSeconds, also add fetch to UIBackgroundModes and register the background task identifier:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>org.traccar.client.heartbeat</string>
</array>
Note: iOS schedules BGAppRefreshTask at its discretion — the interval is a "no sooner than" hint, not a guarantee.
Diagnostic log
getLogs() returns the SDK's internal log entries, oldest first. Each LogEntry carries time (epoch ms) and message. Useful for surfacing tracker state in a debug screen. clearLogs() empties the store.
License
Apache License 2.0. See LICENSE.